1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3
4"""
5Gradio inference app for:
6 AlgoDriveAI/Akkadian_English_DenseLLM_1B
7
8Fixes included:
9- Does NOT trust bad/partial config.json values for architecture.
10- Infers core architecture from pytorch_model.bin tensor shapes first.
11- Uses the training MLA architecture defaults:
12 d_model=1536
13 n_layers=36
14 n_heads=12
15 q_lora_rank=768
16 kv_lora_rank=384
17 qk_nope_head_dim=64
18 qk_rope_head_dim=64
19 v_head_dim=128
20 ff_hidden_mult=3.5
21 max_seq_len=4096
22- Defines DenseLLM directly in this script.
23- Does NOT require modeling_dense_llm.py to be uploaded.
24- Launches a streaming Gradio UI.
25
26Install:
27 pip install torch transformers huggingface_hub gradio
28"""
29
30import os
31import re
32import json
33from dataclasses import dataclass
34from typing import Optional, Dict, Any
35
36import torch
37import torch.nn as nn
38import torch.nn.functional as F
39import gradio as gr
40
41from huggingface_hub import hf_hub_download
42from transformers import AutoTokenizer
43
44
45# =============================================================================
46# REPO SETTINGS
47# =============================================================================
48
49REPO_ID = "AlgoDriveAI/Akkadian_English_DenseLLM_1B"
50
51CONFIG_FILENAME = "config.json"
52WEIGHTS_FILENAME = "pytorch_model.bin"
53
54FALLBACK_TOKENIZER = "mistralai/Mistral-7B-Instruct-v0.3"
55DOC_EOS_TOKEN = "<|endoftext|>"
56
57
58# =============================================================================
59# KNOWN TRAINING ARCHITECTURE FALLBACKS
60# =============================================================================
61
62TRAINING_D_MODEL = 1536
63TRAINING_N_LAYERS = 36
64TRAINING_N_HEADS = 12
65
66TRAINING_Q_LORA_RANK = 768
67TRAINING_KV_LORA_RANK = 384
68
69TRAINING_QK_NOPE_HEAD_DIM = 64
70TRAINING_QK_ROPE_HEAD_DIM = 64
71TRAINING_V_HEAD_DIM = 128
72
73TRAINING_FF_MULT = 3.5
74TRAINING_QK_NORM = True
75TRAINING_MAX_SEQ_LEN = 4096
76
77
78# =============================================================================
79# MODEL ARCHITECTURE
80# =============================================================================
81
82try:
83 from torch.nn import RMSNorm
84except ImportError:
85 class RMSNorm(nn.Module):
86 def __init__(self, normalized_shape, eps: float = 1e-6):
87 super().__init__()
88 if isinstance(normalized_shape, int):
89 normalized_shape = (normalized_shape,)
90 self.eps = eps
91 self.weight = nn.Parameter(torch.ones(normalized_shape))
92
93 def forward(self, x: torch.Tensor) -> torch.Tensor:
94 return self.weight * (
95 x.float()
96 * torch.rsqrt(x.float().pow(2).mean(-1, keepdim=True) + self.eps)
97 ).to(x.dtype)
98
99
100class RotaryEmbedding(nn.Module):
101 def __init__(self, dim: int, base: float = 10000.0, max_seq_len: int = 8192):
102 super().__init__()
103
104 inv_freq = 1.0 / (
105 base ** (torch.arange(0, dim, 2, dtype=torch.float32) / dim)
106 )
107
108 t = torch.arange(max_seq_len, dtype=torch.float32)
109 freqs = torch.outer(t, inv_freq)
110
111 self.register_buffer(
112 "cos_cached",
113 freqs.cos().repeat(1, 2),
114 persistent=False,
115 )
116
117 self.register_buffer(
118 "sin_cached",
119 freqs.sin().repeat(1, 2),
120 persistent=False,
121 )
122
123 def forward(self, seq_len: int, dtype: torch.dtype):
124 return (
125 self.cos_cached[:seq_len].to(dtype),
126 self.sin_cached[:seq_len].to(dtype),
127 )
128
129
130def _rotate_half(x: torch.Tensor) -> torch.Tensor:
131 half = x.shape[-1] // 2
132 return torch.cat([-x[..., half:], x[..., :half]], dim=-1)
133
134
135def apply_rotary_emb(
136 q: torch.Tensor,
137 k: torch.Tensor,
138 cos: torch.Tensor,
139 sin: torch.Tensor,
140):
141 cos = cos.unsqueeze(0).unsqueeze(0)
142 sin = sin.unsqueeze(0).unsqueeze(0)
143
144 q_out = (q * cos) + (_rotate_half(q) * sin)
145 k_out = (k * cos) + (_rotate_half(k) * sin)
146
147 return q_out, k_out
148
149
150class MLA(nn.Module):
151 """
152 MLA-style attention matching the training code:
153 - Q compression
154 - fused KV down projection
155 - fused KV up projection
156 - shared k_rope
157 - optional QK norm
158 """
159
160 def __init__(
161 self,
162 d_model: int,
163 n_heads: int,
164 q_lora_rank: int,
165 kv_lora_rank: int,
166 qk_nope_head_dim: int,
167 qk_rope_head_dim: int,
168 v_head_dim: int,
169 rope: RotaryEmbedding,
170 qk_norm: bool = False,
171 attn_dropout: float = 0.0,
172 ):
173 super().__init__()
174
175 self.n_heads = n_heads
176 self.q_lora_rank = q_lora_rank
177 self.kv_lora_rank = kv_lora_rank
178 self.qk_nope_head_dim = qk_nope_head_dim
179 self.qk_rope_head_dim = qk_rope_head_dim
180 self.q_head_dim = qk_nope_head_dim + qk_rope_head_dim
181 self.v_head_dim = v_head_dim
182 self.attn_drop = attn_dropout
183 self.rope = rope
184
185 self.q_a_proj = nn.Linear(d_model, q_lora_rank, bias=False)
186 self.q_a_norm = RMSNorm(q_lora_rank)
187 self.q_b_proj = nn.Linear(
188 q_lora_rank,
189 n_heads * self.q_head_dim,
190 bias=False,
191 )
192
193 self.kv_a_proj = nn.Linear(
194 d_model,
195 kv_lora_rank + qk_rope_head_dim,
196 bias=False,
197 )
198 self.kv_a_norm = RMSNorm(kv_lora_rank)
199
200 self.kv_b_proj = nn.Linear(
201 kv_lora_rank,
202 n_heads * (qk_nope_head_dim + v_head_dim),
203 bias=False,
204 )
205
206 self.o_proj = nn.Linear(
207 n_heads * v_head_dim,
208 d_model,
209 bias=False,
210 )
211 self.o_proj._is_residual = True
212
213 self.qk_norm = qk_norm
214 if qk_norm:
215 self.q_nope_norm = RMSNorm(qk_nope_head_dim)
216 self.k_nope_norm = RMSNorm(qk_nope_head_dim)
217
218 def forward(self, x: torch.Tensor) -> torch.Tensor:
219 B, T, _ = x.shape
220
221 H = self.n_heads
222 nope_dim = self.qk_nope_head_dim
223 rope_dim = self.qk_rope_head_dim
224 v_dim = self.v_head_dim
225 q_dim = self.q_head_dim
226
227 q = self.q_b_proj(self.q_a_norm(self.q_a_proj(x)))
228 q = q.view(B, T, H, q_dim)
229
230 q_nope = q[..., :nope_dim]
231 q_rope = q[..., nope_dim:]
232
233 kv_a = self.kv_a_proj(x)
234 c, k_rope = torch.split(
235 kv_a,
236 [self.kv_lora_rank, rope_dim],
237 dim=-1,
238 )
239
240 c = self.kv_a_norm(c)
241 k_rope = k_rope.view(B, T, 1, rope_dim)
242
243 kv = self.kv_b_proj(c)
244 kv = kv.view(B, T, H, nope_dim + v_dim)
245
246 k_nope, v = torch.split(kv, [nope_dim, v_dim], dim=-1)
247
248 if self.qk_norm:
249 q_nope = self.q_nope_norm(q_nope)
250 k_nope = self.k_nope_norm(k_nope)
251
252 q_nope = q_nope.transpose(1, 2)
253 q_rope = q_rope.transpose(1, 2)
254 k_nope = k_nope.transpose(1, 2)
255 k_rope = k_rope.transpose(1, 2)
256 v = v.transpose(1, 2)
257
258 cos, sin = self.rope(T, x.dtype)
259 q_rope, k_rope = apply_rotary_emb(q_rope, k_rope, cos, sin)
260
261 q = torch.cat([q_nope, q_rope], dim=-1)
262
263 k_rope = k_rope.expand(B, H, T, rope_dim)
264 k = torch.cat([k_nope, k_rope], dim=-1)
265
266 drop_p = self.attn_drop if self.training else 0.0
267
268 out = F.scaled_dot_product_attention(
269 q,
270 k,
271 v,
272 dropout_p=drop_p,
273 is_causal=True,
274 )
275
276 out = out.transpose(1, 2).reshape(B, T, H * v_dim)
277
278 return self.o_proj(out)
279
280
281class SwiGLU(nn.Module):
282 def __init__(self, d_model: int, hidden_mult: float = 3.5):
283 super().__init__()
284
285 inner = int(hidden_mult * d_model)
286 inner = ((inner + 255) // 256) * 256
287
288 self.gate_up_proj = nn.Linear(d_model, 2 * inner, bias=False)
289 self.down_proj = nn.Linear(inner, d_model, bias=False)
290 self.down_proj._is_residual = True
291
292 def forward(self, x: torch.Tensor) -> torch.Tensor:
293 gate, up = self.gate_up_proj(x).chunk(2, dim=-1)
294 return self.down_proj(F.silu(gate) * up)
295
296
297class Block(nn.Module):
298 def __init__(
299 self,
300 d_model: int,
301 n_heads: int,
302 q_lora_rank: int,
303 kv_lora_rank: int,
304 qk_nope_head_dim: int,
305 qk_rope_head_dim: int,
306 v_head_dim: int,
307 rope: RotaryEmbedding,
308 ff_hidden_mult: float = 3.5,
309 qk_norm: bool = False,
310 attn_dropout: float = 0.0,
311 resid_dropout: float = 0.0,
312 ):
313 super().__init__()
314
315 self.ln_attn = RMSNorm(d_model)
316 self.ln_ff = RMSNorm(d_model)
317
318 self.attn = MLA(
319 d_model=d_model,
320 n_heads=n_heads,
321 q_lora_rank=q_lora_rank,
322 kv_lora_rank=kv_lora_rank,
323 qk_nope_head_dim=qk_nope_head_dim,
324 qk_rope_head_dim=qk_rope_head_dim,
325 v_head_dim=v_head_dim,
326 rope=rope,
327 qk_norm=qk_norm,
328 attn_dropout=attn_dropout,
329 )
330
331 self.ff = SwiGLU(
332 d_model=d_model,
333 hidden_mult=ff_hidden_mult,
334 )
335
336 self.resid_drop = (
337 nn.Dropout(resid_dropout)
338 if resid_dropout > 0
339 else nn.Identity()
340 )
341
342 def forward(self, x: torch.Tensor) -> torch.Tensor:
343 x = x + self.resid_drop(self.attn(self.ln_attn(x)))
344 x = x + self.resid_drop(self.ff(self.ln_ff(x)))
345 return x
346
347
348@dataclass
349class ModelConfig:
350 vocab_size: int
351 d_model: int
352 n_layers: int
353 n_heads: int
354 q_lora_rank: int
355 kv_lora_rank: int
356 qk_nope_head_dim: int
357 qk_rope_head_dim: int
358 v_head_dim: int
359 ff_hidden_mult: float
360 qk_norm: bool
361
362 max_seq_len: int = 4096
363 attn_dropout: float = 0.0
364 resid_dropout: float = 0.0
365 emb_dropout: float = 0.0
366 label_smoothing: float = 0.0
367
368 @property
369 def q_head_dim(self) -> int:
370 return self.qk_nope_head_dim + self.qk_rope_head_dim
371
372
373class DenseLLM(nn.Module):
374 def __init__(
375 self,
376 cfg: ModelConfig,
377 use_gradient_checkpointing: bool = False,
378 ):
379 super().__init__()
380
381 self.cfg = cfg
382 self.use_gradient_checkpointing = use_gradient_checkpointing
383
384 self.embed = nn.Embedding(cfg.vocab_size, cfg.d_model)
385
386 self.emb_drop = (
387 nn.Dropout(cfg.emb_dropout)
388 if cfg.emb_dropout > 0
389 else nn.Identity()
390 )
391
392 self.rope = RotaryEmbedding(
393 dim=cfg.qk_rope_head_dim,
394 max_seq_len=cfg.max_seq_len,
395 )
396
397 self.blocks = nn.ModuleList([
398 Block(
399 d_model=cfg.d_model,
400 n_heads=cfg.n_heads,
401 q_lora_rank=cfg.q_lora_rank,
402 kv_lora_rank=cfg.kv_lora_rank,
403 qk_nope_head_dim=cfg.qk_nope_head_dim,
404 qk_rope_head_dim=cfg.qk_rope_head_dim,
405 v_head_dim=cfg.v_head_dim,
406 rope=self.rope,
407 ff_hidden_mult=cfg.ff_hidden_mult,
408 qk_norm=cfg.qk_norm,
409 attn_dropout=cfg.attn_dropout,
410 resid_dropout=cfg.resid_dropout,
411 )
412 for _ in range(cfg.n_layers)
413 ])
414
415 self.ln_f = RMSNorm(cfg.d_model)
416 self.lm_head = nn.Linear(cfg.d_model, cfg.vocab_size, bias=False)
417
418 self.apply(self._init_weights)
419
420 scale = (2 * cfg.n_layers) ** -0.5
421 for module in self.modules():
422 if getattr(module, "_is_residual", False):
423 with torch.no_grad():
424 module.weight.mul_(scale)
425
426 self.lm_head.weight = self.embed.weight
427
428 @staticmethod
429 def _init_weights(module: nn.Module):
430 if isinstance(module, nn.Linear):
431 nn.init.normal_(module.weight, mean=0.0, std=0.02)
432 elif isinstance(module, nn.Embedding):
433 nn.init.normal_(module.weight, mean=0.0, std=0.02)
434
435 def forward(
436 self,
437 idx: torch.Tensor,
438 targets: Optional[torch.Tensor] = None,
439 ):
440 x = self.emb_drop(self.embed(idx))
441
442 for block in self.blocks:
443 x = block(x)
444
445 logits = self.lm_head(self.ln_f(x))
446
447 loss = None
448
449 if targets is not None:
450 loss = F.cross_entropy(
451 logits[:, :-1].contiguous().view(-1, logits.size(-1)),
452 targets[:, 1:].contiguous().view(-1),
453 label_smoothing=self.cfg.label_smoothing,
454 )
455
456 return logits, loss
457
458
459# =============================================================================
460# LOADING HELPERS
461# =============================================================================
462
463def load_json_from_hf(repo_id: str, filename: str):
464 path = hf_hub_download(
465 repo_id=repo_id,
466 filename=filename,
467 )
468
469 with open(path, "r", encoding="utf-8") as f:
470 data = json.load(f)
471
472 return data, path
473
474
475def load_state_dict_safely(weights_path: str):
476 try:
477 obj = torch.load(
478 weights_path,
479 map_location="cpu",
480 weights_only=True,
481 )
482 except TypeError:
483 obj = torch.load(weights_path, map_location="cpu")
484 except Exception:
485 obj = torch.load(weights_path, map_location="cpu")
486
487 if isinstance(obj, dict):
488 if "model" in obj:
489 obj = obj["model"]
490 elif "model_state_dict" in obj:
491 obj = obj["model_state_dict"]
492 elif "state_dict" in obj:
493 obj = obj["state_dict"]
494
495 if not isinstance(obj, dict):
496 raise TypeError("Loaded weights object is not a state_dict dictionary.")
497
498 cleaned = {}
499
500 for key, value in obj.items():
501 if key.startswith("_orig_mod."):
502 key = key.removeprefix("_orig_mod.")
503 if key.startswith("module."):
504 key = key.removeprefix("module.")
505 cleaned[key] = value
506
507 return cleaned
508
509
510def find_key(state_dict: Dict[str, torch.Tensor], *suffixes: str) -> Optional[str]:
511 """
512 Finds a key by exact match first, then by suffix.
513 Useful if the checkpoint has prefixes.
514 """
515 for suffix in suffixes:
516 if suffix in state_dict:
517 return suffix
518
519 for key in state_dict.keys():
520 for suffix in suffixes:
521 if key.endswith(suffix):
522 return key
523
524 return None
525
526
527def infer_n_layers(state_dict: Dict[str, torch.Tensor]) -> Optional[int]:
528 block_indices = []
529
530 for key in state_dict.keys():
531 match = re.search(r"(?:^|\.)blocks\.(\d+)\.", key)
532 if match:
533 block_indices.append(int(match.group(1)))
534
535 if not block_indices:
536 return None
537
538 return max(block_indices) + 1
539
540
541def choose_n_heads(
542 q_b_out: int,
543 o_proj_in: int,
544 d_model: int,
545 raw_config: Dict[str, Any],
546) -> int:
547 """
548 Chooses n_heads from checkpoint shapes.
549
550 For this training run:
551 q_b_out = n_heads * q_head_dim
552 o_proj_in = n_heads * v_head_dim
553 q_head_dim = 128
554 v_head_dim = 128
555 n_heads = 12
556 d_model = 1536
557 """
558
559 candidates = []
560
561 # Prefer raw config only if it is compatible with checkpoint shapes.
562 for key in ["n_heads", "num_attention_heads", "num_heads"]:
563 if key in raw_config:
564 try:
565 h = int(raw_config[key])
566 if h > 0 and q_b_out % h == 0 and o_proj_in % h == 0:
567 q_head_dim = q_b_out // h
568 v_head_dim = o_proj_in // h
569 candidates.append((h, q_head_dim, v_head_dim, "raw_config"))
570 except Exception:
571 pass
572
573 # Add known training value if compatible.
574 h = TRAINING_N_HEADS
575 if q_b_out % h == 0 and o_proj_in % h == 0:
576 candidates.append((h, q_b_out // h, o_proj_in // h, "training_default"))
577
578 # General divisors.
579 for h in range(1, 129):
580 if q_b_out % h == 0 and o_proj_in % h == 0:
581 q_head_dim = q_b_out // h
582 v_head_dim = o_proj_in // h
583 candidates.append((h, q_head_dim, v_head_dim, "divisor_search"))
584
585 # Best case: q_head_dim == v_head_dim == 128 and h * v_head_dim == d_model.
586 for h, qhd, vhd, source in candidates:
587 if qhd == 128 and vhd == 128 and h * vhd == d_model:
588 return h
589
590 # Next: q_head_dim == v_head_dim and h * v_head_dim == d_model.
591 for h, qhd, vhd, source in candidates:
592 if qhd == vhd and h * vhd == d_model:
593 return h
594
595 # Next: known training default if compatible.
596 for h, qhd, vhd, source in candidates:
597 if source == "training_default":
598 return h
599
600 # Last: raw config if compatible.
601 for h, qhd, vhd, source in candidates:
602 if source == "raw_config":
603 return h
604
605 raise ValueError(
606 f"Could not infer n_heads from shapes: q_b_out={q_b_out}, "
607 f"o_proj_in={o_proj_in}, d_model={d_model}"
608 )
609
610
611def build_model_config_from_checkpoint(
612 state_dict: Dict[str, torch.Tensor],
613 raw_config: Dict[str, Any],
614) -> Dict[str, Any]:
615 """
616 Build ModelConfig from checkpoint shapes first.
617
618 This avoids trusting partial or misleading config.json values.
619 The checkpoint is the source of truth.
620 """
621
622 embed_key = find_key(state_dict, "embed.weight")
623 q_a_key = find_key(state_dict, "blocks.0.attn.q_a_proj.weight")
624 q_b_key = find_key(state_dict, "blocks.0.attn.q_b_proj.weight")
625 kv_a_key = find_key(state_dict, "blocks.0.attn.kv_a_proj.weight")
626 kv_b_key = find_key(state_dict, "blocks.0.attn.kv_b_proj.weight")
627 o_proj_key = find_key(state_dict, "blocks.0.attn.o_proj.weight")
628 gate_up_key = find_key(state_dict, "blocks.0.ff.gate_up_proj.weight")
629
630 required_keys = {
631 "embed.weight": embed_key,
632 "blocks.0.attn.q_a_proj.weight": q_a_key,
633 "blocks.0.attn.q_b_proj.weight": q_b_key,
634 "blocks.0.attn.kv_a_proj.weight": kv_a_key,
635 "blocks.0.attn.kv_b_proj.weight": kv_b_key,
636 "blocks.0.attn.o_proj.weight": o_proj_key,
637 "blocks.0.ff.gate_up_proj.weight": gate_up_key,
638 }
639
640 missing = [name for name, key in required_keys.items() if key is None]
641 if missing:
642 print("\nAvailable state_dict keys sample:")
643 for k in list(state_dict.keys())[:80]:
644 print(" ", k)
645 raise KeyError("Missing expected checkpoint keys: " + ", ".join(missing))
646
647 embed = state_dict[embed_key]
648 q_a = state_dict[q_a_key]
649 q_b = state_dict[q_b_key]
650 kv_a = state_dict[kv_a_key]
651 kv_b = state_dict[kv_b_key]
652 o_proj = state_dict[o_proj_key]
653 gate_up = state_dict[gate_up_key]
654
655 vocab_size = int(embed.shape[0])
656 d_model = int(embed.shape[1])
657
658 n_layers = infer_n_layers(state_dict)
659 if n_layers is None:
660 n_layers = TRAINING_N_LAYERS
661
662 q_lora_rank = int(q_a.shape[0])
663
664 q_b_out = int(q_b.shape[0])
665 kv_a_out = int(kv_a.shape[0])
666 kv_b_out = int(kv_b.shape[0])
667 o_proj_in = int(o_proj.shape[1])
668
669 n_heads = choose_n_heads(
670 q_b_out=q_b_out,
671 o_proj_in=o_proj_in,
672 d_model=d_model,
673 raw_config=raw_config,
674 )
675
676 q_head_dim = q_b_out // n_heads
677 v_head_dim = o_proj_in // n_heads
678
679 # Prefer the training split of q_head_dim=64+64 when q_head_dim=128.
680 if q_head_dim == (
681 TRAINING_QK_NOPE_HEAD_DIM + TRAINING_QK_ROPE_HEAD_DIM
682 ):
683 qk_nope_head_dim = TRAINING_QK_NOPE_HEAD_DIM
684 qk_rope_head_dim = TRAINING_QK_ROPE_HEAD_DIM
685 else:
686 # Fallback: split evenly.
687 qk_nope_head_dim = q_head_dim // 2
688 qk_rope_head_dim = q_head_dim - qk_nope_head_dim
689
690 kv_lora_rank = kv_a_out - qk_rope_head_dim
691
692 # Cross-check kv_b shape:
693 # kv_b_out = n_heads * (qk_nope_head_dim + v_head_dim)
694 expected_kv_b_out = n_heads * (qk_nope_head_dim + v_head_dim)
695
696 if kv_b_out != expected_kv_b_out:
697 # Try the training defaults before failing.
698 qk_nope_head_dim = TRAINING_QK_NOPE_HEAD_DIM
699 qk_rope_head_dim = TRAINING_QK_ROPE_HEAD_DIM
700 v_head_dim = TRAINING_V_HEAD_DIM
701 kv_lora_rank = kv_a_out - qk_rope_head_dim
702 expected_kv_b_out = n_heads * (qk_nope_head_dim + v_head_dim)
703
704 if kv_b_out != expected_kv_b_out:
705 raise ValueError(
706 "Could not reconcile kv_b shape.\n"
707 f"kv_b_out={kv_b_out}\n"
708 f"expected={expected_kv_b_out}\n"
709 f"n_heads={n_heads}, nope={qk_nope_head_dim}, v={v_head_dim}"
710 )
711
712 inner = int(gate_up.shape[0]) // 2
713
714 inferred_ff_mult = inner / float(d_model)
715
716 training_inner = ((int(TRAINING_FF_MULT * d_model) + 255) // 256) * 256
717 if training_inner == inner:
718 ff_hidden_mult = TRAINING_FF_MULT
719 else:
720 ff_hidden_mult = inferred_ff_mult
721
722 max_seq_len = raw_config.get(
723 "max_seq_len",
724 raw_config.get("context_len", TRAINING_MAX_SEQ_LEN),
725 )
726
727 cfg = {
728 "vocab_size": vocab_size,
729 "d_model": d_model,
730 "n_layers": n_layers,
731 "n_heads": n_heads,
732 "q_lora_rank": q_lora_rank,
733 "kv_lora_rank": kv_lora_rank,
734 "qk_nope_head_dim": qk_nope_head_dim,
735 "qk_rope_head_dim": qk_rope_head_dim,
736 "v_head_dim": v_head_dim,
737 "ff_hidden_mult": ff_hidden_mult,
738 "qk_norm": bool(raw_config.get("qk_norm", TRAINING_QK_NORM)),
739 "max_seq_len": int(max_seq_len),
740
741 # Inference-time dropout/smoothing should be off.
742 "attn_dropout": 0.0,
743 "resid_dropout": 0.0,
744 "emb_dropout": 0.0,
745 "label_smoothing": 0.0,
746 }
747
748 if cfg["qk_nope_head_dim"] + cfg["qk_rope_head_dim"] != cfg["v_head_dim"]:
749 raise ValueError(
750 f"Bad inferred config: q_head_dim="
751 f"{cfg['qk_nope_head_dim'] + cfg['qk_rope_head_dim']} "
752 f"but v_head_dim={cfg['v_head_dim']}"
753 )
754
755 if cfg["d_model"] != cfg["n_heads"] * cfg["v_head_dim"]:
756 raise ValueError(
757 f"Bad inferred config: d_model={cfg['d_model']} but "
758 f"n_heads*v_head_dim={cfg['n_heads'] * cfg['v_head_dim']}"
759 )
760
761 return cfg
762
763
764def load_tokenizer_for_model(
765 repo_id: str,
766 raw_config: Dict[str, Any],
767 target_vocab_size: int,
768):
769 """
770 Loads tokenizer.
771
772 First tries repo tokenizer. If it looks suspiciously tiny, falls back to
773 the training tokenizer from the training script.
774 """
775
776 tokenizer = None
777
778 try:
779 print("Loading tokenizer from model repo...")
780 tokenizer = AutoTokenizer.from_pretrained(repo_id, use_fast=True)
781 print(f"Repo tokenizer loaded. vocab={len(tokenizer):,}")
782 except Exception as e:
783 print(f"Could not load tokenizer from repo: {e}")
784
785 if tokenizer is not None and len(tokenizer) < 1000 and target_vocab_size > 1000:
786 print(
787 f"Repo tokenizer looks too small: vocab={len(tokenizer):,}, "
788 f"model vocab={target_vocab_size:,}. Ignoring repo tokenizer."
789 )
790 tokenizer = None
791
792 if tokenizer is None:
793 fallback_name = raw_config.get("vocab_name", FALLBACK_TOKENIZER)
794 print(f"Loading fallback tokenizer: {fallback_name}")
795 tokenizer = AutoTokenizer.from_pretrained(fallback_name, use_fast=True)
796
797 doc_eos_token = raw_config.get("doc_eos_token", DOC_EOS_TOKEN)
798
799 if doc_eos_token not in tokenizer.get_vocab():
800 tokenizer.add_special_tokens({
801 "additional_special_tokens": [doc_eos_token],
802 })
803
804 tokenizer.doc_eos_token = doc_eos_token
805 tokenizer.doc_eos_token_id = tokenizer.convert_tokens_to_ids(doc_eos_token)
806
807 if tokenizer.pad_token is None:
808 tokenizer.pad_token = doc_eos_token
809 tokenizer.pad_token_id = tokenizer.doc_eos_token_id
810
811 if len(tokenizer) < target_vocab_size:
812 needed = target_vocab_size - len(tokenizer)
813 print(f"Adding {needed:,} dummy tokens to match model vocab_size={target_vocab_size:,}")
814 tokenizer.add_tokens(
815 [f"<|dummy_infer_{i}|>" for i in range(needed)],
816 special_tokens=False,
817 )
818
819 if len(tokenizer) > target_vocab_size:
820 print(
821 f"WARNING: tokenizer vocab={len(tokenizer):,} is larger than "
822 f"model vocab={target_vocab_size:,}.\n"
823 "Input token IDs above model vocab will be remapped to EOS/doc token.\n"
824 "For best quality, upload the exact tokenizer files saved during training."
825 )
826
827 tokenizer.model_max_length = int(1e9)
828
829 print(
830 f"Tokenizer ready. tokenizer_vocab={len(tokenizer):,}, "
831 f"model_vocab={target_vocab_size:,}, eos_id={tokenizer.doc_eos_token_id}"
832 )
833
834 return tokenizer
835
836
837def sanitize_input_ids(
838 input_ids: torch.Tensor,
839 model_vocab_size: int,
840 fallback_token_id: int,
841):
842 """
843 Prevent embedding-index errors if fallback tokenizer produces IDs outside
844 the model vocab.
845 """
846
847 if input_ids.numel() == 0:
848 return input_ids
849
850 if input_ids.max().item() >= model_vocab_size:
851 input_ids = input_ids.clone()
852 input_ids[input_ids >= model_vocab_size] = fallback_token_id
853
854 return input_ids
855
856
857# =============================================================================
858# LOAD CONFIG, WEIGHTS, TOKENIZER, MODEL
859# =============================================================================
860
861print("Downloading config...")
862raw_config, config_path = load_json_from_hf(REPO_ID, CONFIG_FILENAME)
863print(f"Config path: {config_path}")
864
865print("\nDownloading weights...")
866weights_path = hf_hub_download(
867 repo_id=REPO_ID,
868 filename=WEIGHTS_FILENAME,
869)
870print(f"Weights path: {weights_path}")
871
872print("\nLoading state dict...")
873state_dict = load_state_dict_safely(weights_path)
874
875print("\nBuilding model config from checkpoint tensor shapes...")
876config = build_model_config_from_checkpoint(state_dict, raw_config)
877model_cfg = ModelConfig(**config)
878
879print("\nFinal model config:")
880for key, value in config.items():
881 print(f" {key}: {value}")
882
883print("\nLoading tokenizer...")
884tokenizer = load_tokenizer_for_model(
885 repo_id=REPO_ID,
886 raw_config=raw_config,
887 target_vocab_size=model_cfg.vocab_size,
888)
889
890fallback_token_id = getattr(tokenizer, "doc_eos_token_id", None)
891if fallback_token_id is None or fallback_token_id >= model_cfg.vocab_size:
892 fallback_token_id = tokenizer.eos_token_id
893
894if fallback_token_id is None or fallback_token_id >= model_cfg.vocab_size:
895 fallback_token_id = 0
896
897
898device = "cuda" if torch.cuda.is_available() else "cpu"
899
900if device == "cuda":
901 torch.set_float32_matmul_precision("high")
902 torch.backends.cuda.matmul.allow_tf32 = True
903 torch.backends.cudnn.allow_tf32 = True
904
905 try:
906 torch.backends.cuda.enable_flash_sdp(True)
907 torch.backends.cuda.enable_mem_efficient_sdp(True)
908 torch.backends.cuda.enable_math_sdp(True)
909 except Exception:
910 pass
911
912 if torch.cuda.is_bf16_supported():
913 dtype = torch.bfloat16
914 else:
915 dtype = torch.float16
916else:
917 dtype = torch.float32
918
919print(f"\nUsing device={device}, dtype={dtype}")
920
921print("\nBuilding model...")
922model = DenseLLM(
923 model_cfg,
924 use_gradient_checkpointing=False,
925).to(device=device, dtype=dtype)
926
927print("Loading model weights...")
928
929try:
930 model.load_state_dict(state_dict, strict=True)
931 print("Weights loaded with strict=True.")
932except RuntimeError as e:
933 print("Strict load failed.")
934 print(str(e)[:4000])
935 raise
936
937model.eval()
938
939print("\nModel ready!")
940
941
942# =============================================================================
943# STREAMING GENERATION
944# =============================================================================
945
946@torch.inference_mode()
947def stream_generate(
948 prompt: str,
949 max_new_tokens: int = 256,
950 temperature: float = 0.55,
951 top_k: int = 35,
952 top_p: float = 0.88,
953 repetition_penalty: float = 1.1,
954):
955 if not prompt or not prompt.strip():
956 yield ""
957 return
958
959 encoded = tokenizer(
960 prompt,
961 return_tensors="pt",
962 add_special_tokens=False,
963 )
964
965 input_ids = encoded["input_ids"]
966
967 input_ids = sanitize_input_ids(
968 input_ids=input_ids,
969 model_vocab_size=model_cfg.vocab_size,
970 fallback_token_id=fallback_token_id,
971 ).to(device)
972
973 generated = input_ids.clone()
974 prompt_len = input_ids.shape[1]
975
976 eos_id = getattr(tokenizer, "doc_eos_token_id", None)
977 if eos_id is None:
978 eos_id = tokenizer.eos_token_id
979
980 if eos_id is not None and eos_id >= model_cfg.vocab_size:
981 eos_id = None
982
983 max_seq_len = int(model_cfg.max_seq_len)
984
985 for _ in range(int(max_new_tokens)):
986 model_input = generated[:, -max_seq_len:]
987
988 logits, _ = model(model_input, None)
989 next_logits = logits[:, -1, :].float()
990
991 if temperature <= 0:
992 next_token = torch.argmax(next_logits, dim=-1, keepdim=True)
993 else:
994 next_logits = next_logits / max(float(temperature), 1e-8)
995
996 # Repetition penalty
997 if repetition_penalty and repetition_penalty != 1.0:
998 used_tokens = torch.unique(generated[0])
999 used_tokens = used_tokens[used_tokens < model_cfg.vocab_size]
1000
1001 if used_tokens.numel() > 0:
1002 token_scores = next_logits[0, used_tokens]
1003 next_logits[0, used_tokens] = torch.where(
1004 token_scores > 0,
1005 token_scores / repetition_penalty,
1006 token_scores * repetition_penalty,
1007 )
1008
1009 # Top-k filtering
1010 if top_k and top_k > 0:
1011 k = min(int(top_k), next_logits.size(-1))
1012 values, _ = torch.topk(next_logits, k)
1013 cutoff = values[:, [-1]]
1014 next_logits[next_logits < cutoff] = -float("inf")
1015
1016 # Top-p / nucleus filtering
1017 if top_p and top_p < 1.0:
1018 sorted_logits, sorted_indices = torch.sort(next_logits, descending=True)
1019 sorted_probs = F.softmax(sorted_logits, dim=-1)
1020 cumulative_probs = torch.cumsum(sorted_probs, dim=-1)
1021
1022 remove_mask = cumulative_probs > float(top_p)
1023 remove_mask[..., 1:] = remove_mask[..., :-1].clone()
1024 remove_mask[..., 0] = False
1025
1026 full_mask = torch.zeros_like(next_logits, dtype=torch.bool)
1027 full_mask.scatter_(1, sorted_indices, remove_mask)
1028 next_logits[full_mask] = -float("inf")
1029
1030 probs = F.softmax(next_logits, dim=-1)
1031
1032 if (
1033 not torch.isfinite(probs).all()
1034 or (probs.sum(dim=-1) <= 0).any()
1035 ):
1036 next_token = torch.argmax(logits[:, -1, :], dim=-1, keepdim=True)
1037 else:
1038 next_token = torch.multinomial(probs, num_samples=1)
1039
1040 generated = torch.cat([generated, next_token], dim=-1)
1041
1042 if eos_id is not None and next_token.item() == eos_id:
1043 break
1044
1045 decoded = tokenizer.decode(
1046 generated[0, prompt_len:],
1047 skip_special_tokens=True,
1048 clean_up_tokenization_spaces=False,
1049 )
1050
1051 yield decoded
1052
1053
1054def respond(
1055 prompt,
1056 max_tokens,
1057 temperature,
1058 top_k,
1059 top_p,
1060 repetition_penalty,
1061):
1062 for partial in stream_generate(
1063 prompt=prompt,
1064 max_new_tokens=max_tokens,
1065 temperature=temperature,
1066 top_k=top_k,
1067 top_p=top_p,
1068 repetition_penalty=repetition_penalty,
1069 ):
1070 yield partial
1071
1072
1073# =============================================================================
1074# GRADIO UI
1075# =============================================================================
1076
1077DEFAULT_PROMPT = """Translate the following Akkadian transliteration into English. Include a literal word-by-word gloss:
1078šarrum bītam rabiam ana ilim ibni.
1079"""
1080
1081EXAMPLES = [
1082 [
1083 "Translate the following Akkadian transliteration into English. Include a literal word-by-word gloss:\nšarrum bītam rabiam ana ilim ibni."
1084 ],
1085 [
1086 "Translate the following Akkadian transliteration into English. Include grammatical notes:\nṭupšarrum awātim ina ṭuppim išṭur."
1087 ],
1088 [
1089 "Translate the following Akkadian transliteration into English. Provide a literal gloss and smooth English translation:\ntamkārum kaspam ana wardim iddin."
1090 ],
1091 [
1092 "Translate the following Old Babylonian-style Akkadian transliteration into English. Explain the case endings if possible:\nawīlum dannum abul ālim ina mūšim iṣṣur."
1093 ],
1094 [
1095 "Translate the following Akkadian transliteration into English. Give both a literal and natural translation:\nahī ana bīt abīšu īrub."
1096 ],
1097 [
1098 "Translate the following Akkadian transliteration into English. If uncertain, explain the possible alternatives:\nlū ilum šarram u ālam liṣṣur."
1099 ],
1100]
1101
1102with gr.Blocks(
1103 title="Akkadian-English DenseLLM 1B",
1104 theme=gr.themes.Soft(),
1105) as demo:
1106 gr.Markdown(
1107 "# Akkadian-English DenseLLM 1B\n"
1108 "*AlgoDriveAI — custom DenseLLM / MLA architecture for Akkadian and Old Babylonian translation experiments*"
1109 )
1110
1111 with gr.Row():
1112 with gr.Column(scale=3):
1113 prompt_box = gr.Textbox(
1114 label="Prompt",
1115 placeholder="Translate the following Akkadian transliteration into English...",
1116 lines=6,
1117 value=DEFAULT_PROMPT,
1118 )
1119
1120 output_box = gr.Textbox(
1121 label="Output",
1122 lines=16,
1123 interactive=False,
1124 )
1125
1126 with gr.Row():
1127 generate_btn = gr.Button("Generate", variant="primary")
1128 clear_btn = gr.ClearButton(
1129 components=[prompt_box, output_box],
1130 value="Clear",
1131 )
1132
1133 with gr.Column(scale=1):
1134 max_tokens = gr.Slider(
1135 minimum=16,
1136 maximum=768,
1137 value=256,
1138 step=1,
1139 label="Max new tokens",
1140 )
1141
1142 temperature = gr.Slider(
1143 minimum=0.0,
1144 maximum=2.0,
1145 value=0.55,
1146 step=0.05,
1147 label="Temperature",
1148 )
1149
1150 top_k = gr.Slider(
1151 minimum=0,
1152 maximum=100,
1153 value=35,
1154 step=1,
1155 label="Top-K",
1156 )
1157
1158 top_p = gr.Slider(
1159 minimum=0.0,
1160 maximum=1.0,
1161 value=0.88,
1162 step=0.01,
1163 label="Top-P",
1164 )
1165
1166 repetition_penalty = gr.Slider(
1167 minimum=1.0,
1168 maximum=1.5,
1169 value=1.1,
1170 step=0.01,
1171 label="Repetition penalty",
1172 )
1173
1174 gr.Examples(
1175 examples=EXAMPLES,
1176 inputs=prompt_box,
1177 )
1178
1179 generate_btn.click(
1180 fn=respond,
1181 inputs=[
1182 prompt_box,
1183 max_tokens,
1184 temperature,
1185 top_k,
1186 top_p,
1187 repetition_penalty,
1188 ],
1189 outputs=output_box,
1190 )
1191
1192 prompt_box.submit(
1193 fn=respond,
1194 inputs=[
1195 prompt_box,
1196 max_tokens,
1197 temperature,
1198 top_k,
1199 top_p,
1200 repetition_penalty,
1201 ],
1202 outputs=output_box,
1203 )
1204
1205
1206if __name__ == "__main__":
1207 demo.queue()
1208 demo.launch(
1209 server_name="0.0.0.0",
1210 server_port=7860,
1211 share=False,
1212 )