HomeBrewedLabs commited on
Commit
f73da3f
·
verified ·
1 Parent(s): 2e70c2a

Upload folder using huggingface_hub

Browse files
README.md ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ library_name: transformers
4
+ pipeline_tag: text-generation
5
+ tags:
6
+ - text-generation
7
+ - custom-code
8
+ - ml-engineering
9
+ - specialist
10
+ - metallum
11
+ ---
12
+
13
+ ## Metallum-1B
14
+
15
+ A 1.0B-parameter **ML/LLM-engineering specialist**, pretrained **from random
16
+ initialization on a single RTX 5090** — 16.0B tokens in ~13.5 days including
17
+ gates and recoveries — then one bounded knowledge/format SFT stage. Not a
18
+ general chatbot. By **Home Brewed Labs** (project codename MetaLLM).
19
+
20
+ The engineering core of the project is measurement discipline: every training
21
+ stage was gated by frozen batteries with exact checkpoint identity, replay
22
+ accounting (≈1.04B replayed tokens from two hardware incidents, fully
23
+ receipted), independent adversarial review of evaluation machinery, and a
24
+ sealed never-trained-on final holdout (H9) reserved for a single one-time
25
+ qualification.
26
+
27
+ ### Architecture
28
+ 26-layer decoder, d_model 1792, GQA 28Q/14KV, SwiGLU (d_ff 4864), RMSNorm
29
+ pre-norm + QK-norm + z-loss, RoPE θ=500k with **NoPE every 4th layer**
30
+ (length-generalizing retrieval heads), document-masked attention, 40k
31
+ byte-level BPE with FIM sentinels, 2048 native context. Muon (2D) + AdamW,
32
+ WSD schedule, curriculum with short-sequence early phases.
33
+
34
+ ### Loading
35
+ ```python
36
+ from transformers import AutoModelForCausalLM, AutoTokenizer
37
+ model = AutoModelForCausalLM.from_pretrained("HomeBrewedlabs/metallum-1b",
38
+ trust_remote_code=True)
39
+ tok = AutoTokenizer.from_pretrained("HomeBrewedlabs/metallum-1b")
40
+ ```
41
+
42
+ `trust_remote_code=True` is required: this is a custom architecture
43
+ (QK-norm, NoPE interleave, document masking). **Do not** map to a stock
44
+ Llama config — that silently drops QK-norm/NoPE and produces a different
45
+ model than the one measured below. Stock GGUF/vLLM converters reject this
46
+ architecture by design; a faithful llama.cpp port is on the roadmap.
47
+
48
+ ### Evaluation (champion s2b075, frozen batteries, repeated where noted)
49
+ | Capability | Metric | Metallum-1B | Qwen3-1.7B | Qwen3-0.6B | SmolLM2-1.7B |
50
+ |---|---|---|---|---|---|
51
+ | In-domain ML-eng problems | ineval_v1, frozen 150-task exec suite | **7/150** | 7/150 | 3/150 | 0/150 |
52
+ | In-domain code (internal suite) | code_v2 40-task, 2 exact repeats | **32/40** (disclosure: known training overlap) | — | — | — |
53
+ | Long-context retrieval | RULER (90) / needles D1024+D2032 (18) | **86/90** and **18/18** | — | — | — |
54
+ | Base LM efficiency | held-out ML-arXiv BPC | **0.7276** | — | — | — |
55
+ | Final unseen generalization | H9 sealed holdout BPC (one-time run) | **1.3953** | — | — | — |
56
+
57
+ All numbers were produced by frozen evaluators with sealed checkpoint
58
+ identity; the ineval suite was used for model selection during development
59
+ (selection-aware caveat), which is why the untouched H9 result is the number
60
+ that matters for release claims.
61
+
62
+ ### Structured output: by decoding, not by hope
63
+ Base weights score **0/60** on a strict whole-output format suite (JSON
64
+ schema / tool-call / MCQ-letter), and three bounded SFT recipes plus a
65
+ ReST-EM screen all failed to move that (all receipted). This model therefore
66
+ ships with **decode-time structure enforcement**: `serve_metallum.py`
67
+ (included) constrains generation with a token-level JSON grammar +
68
+ schema-forced keys, and scores **60/60** on the same frozen suite. If you
69
+ consume the raw weights without the wrapper, expect prose, not JSON.
70
+
71
+ ```bash
72
+ python serve_metallum.py --port 8399 # localhost endpoint: /generate, modes free|json|tool|mcq
73
+ ```
74
+
75
+ ### Intended use
76
+ ML/LLM-engineering assistant: PyTorch/training scaffolding, concept
77
+ explanation, long-context retrieval over technical docs, structured-output
78
+ endpoints via the wrapper. **Out of scope:** general chat, non-ML factual
79
+ QA, general-purpose coding, safety-critical use, autonomous code execution.
80
+
81
+ ### Honest limitations
82
+ - **neural_ops floor exception:** this checkpoint scores 4/5 on the internal
83
+ neural-ops category vs the 5/5 preregistered floor. The exception was
84
+ adjudicated explicitly by the owner; it is disclosed, not hidden.
85
+ - **MCF (250q multiple-choice) is permanently non-promotable:** the teacher
86
+ generation prompts embedded real MCF items, so no MCF number from this
87
+ lineage is valid evidence. Do not report one.
88
+ - **ineval is selection-aware:** 7/150 was measured on a suite used during
89
+ development. The sealed H9 result (**1.3953 BPC** on 400k post-cutoff
90
+ Wikipedia characters, single run, contamination-verified at 0.0099% shingle
91
+ containment) is the generalization claim.
92
+ - Free generation makes local factual slips; verify specifics.
93
+ - No preference/safety alignment; no RLHF.
94
+ - Format behavior requires the constrained decoder (above).
95
+
96
+ ### Training data
97
+ Pretraining: 19.399B effective tokens (8.473B unique): 53.6% code, 19.6%
98
+ knowledge, 15.2% reasoning, 11.6% math. 1.24% (240.6M tokens) is
99
+ provider-teacher-derived; full lineage map is maintained by the authors.
100
+ SFT (12,496 rows): 40% locally self-hosted open-teacher MCQs
101
+ (qwen2.5-coder:14b, Apache-2.0), 12% self-generated synthetic retrieval rows,
102
+ 48% provider-teacher-derived replay (disposition: owner rights review).
103
+
104
+ **Attribution — Stack Exchange (`stack_ml_edu`, 2.973B pack tokens, 15.3% of
105
+ the pack): CC BY-SA 4.0.** Attribution is owed to Stack Exchange contributors,
106
+ and the share-alike obligation attaches to that portion of the training data.
107
+
108
+ ### License
109
+ Apache-2.0 (model weights + code). See the attribution note above regarding
110
+ training-data share-alike obligations.
111
+
112
+ ### Citation
113
+ Technical report in preparation.
config.json ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "MetaLLMForCausalLM"
4
+ ],
5
+ "attn_impl": "auto",
6
+ "auto_map": {
7
+ "AutoConfig": "configuration_metallm.MetaLLMConfig",
8
+ "AutoModelForCausalLM": "modeling_metallm.MetaLLMForCausalLM"
9
+ },
10
+ "bos_id": 1,
11
+ "bos_token_id": 1,
12
+ "d_ff": 4864,
13
+ "d_model": 1792,
14
+ "doc_mask": true,
15
+ "hidden_size": 1792,
16
+ "max_position_embeddings": 2048,
17
+ "max_seq_len": 2048,
18
+ "model_type": "metallm",
19
+ "n_heads": 28,
20
+ "n_kv_heads": 14,
21
+ "n_layers": 26,
22
+ "nope_every": 4,
23
+ "norm_eps": 1e-05,
24
+ "num_attention_heads": 28,
25
+ "num_hidden_layers": 26,
26
+ "num_key_value_heads": 14,
27
+ "qk_norm": true,
28
+ "rope_fp32": true,
29
+ "rope_theta": 500000.0,
30
+ "tie_embeddings": true,
31
+ "transformers_version": "4.45.0",
32
+ "vocab_size": 40000,
33
+ "z_loss_weight": 0.0001
34
+ }
configuration_metallm.py ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """HF config for MetaLLM / Vishvakarma (custom arch: NoPE-every-N, QK-norm, GQA, SwiGLU).
2
+
3
+ Field names intentionally mirror configs/model.py:ModelConfig so this object can be
4
+ passed directly to metallm_core.MetaLLMv2 as its `cfg` (duck-typed).
5
+ """
6
+ from transformers import PretrainedConfig
7
+
8
+
9
+ class MetaLLMConfig(PretrainedConfig):
10
+ model_type = "metallm"
11
+
12
+ def __init__(
13
+ self,
14
+ vocab_size: int = 40000,
15
+ n_layers: int = 26,
16
+ d_model: int = 1792,
17
+ n_heads: int = 28,
18
+ n_kv_heads: int = 14,
19
+ d_ff: int = 4864,
20
+ max_seq_len: int = 2048,
21
+ rope_theta: float = 500_000.0,
22
+ norm_eps: float = 1e-5,
23
+ tie_embeddings: bool = True,
24
+ qk_norm: bool = True,
25
+ z_loss_weight: float = 0.0, # inference shim: aux loss unused, kept for fidelity
26
+ rope_fp32: bool = True,
27
+ doc_mask: bool = True, # equals plain causal for single-document prompts
28
+ attn_impl: str = "sdpa", # "sdpa" is the portable inference default
29
+ nope_every: int = 4,
30
+ bos_id: int = 1,
31
+ **kwargs,
32
+ ):
33
+ self.vocab_size = vocab_size
34
+ self.n_layers = n_layers
35
+ self.d_model = d_model
36
+ self.n_heads = n_heads
37
+ self.n_kv_heads = n_kv_heads
38
+ self.d_ff = d_ff
39
+ self.max_seq_len = max_seq_len
40
+ self.rope_theta = rope_theta
41
+ self.norm_eps = norm_eps
42
+ self.tie_embeddings = tie_embeddings
43
+ self.qk_norm = qk_norm
44
+ self.z_loss_weight = z_loss_weight
45
+ self.rope_fp32 = rope_fp32
46
+ self.doc_mask = doc_mask
47
+ self.attn_impl = attn_impl
48
+ self.nope_every = nope_every
49
+ self.bos_id = bos_id
50
+ # standard-name aliases: transformers>=5.13 core reads these directly
51
+ self.num_hidden_layers = n_layers
52
+ self.hidden_size = d_model
53
+ self.num_attention_heads = n_heads
54
+ self.num_key_value_heads = n_kv_heads
55
+ self.max_position_embeddings = max_seq_len
56
+ kwargs.setdefault("tie_word_embeddings", tie_embeddings)
57
+ kwargs.setdefault("bos_token_id", bos_id)
58
+ super().__init__(**kwargs)
59
+
60
+ @property
61
+ def head_dim(self) -> int:
62
+ return self.d_model // self.n_heads
generation_config.json ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ {
2
+ "bos_token_id": 1,
3
+ "eos_token_id": 2,
4
+ "pad_token_id": 0,
5
+ "max_length": 2048
6
+ }
metallm_core.py ADDED
@@ -0,0 +1,374 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ MetaLLM v2 — Llama3-style decoder with the 2026 stability/retrieval bundle.
3
+
4
+ Deltas vs v1 (model/llama.py), each gated by a ModelConfig flag so v1 behavior
5
+ is reproducible by leaving the flags off:
6
+
7
+ * doc_mask — document-masked causal attention. Tokens only attend within
8
+ their own document (segment id = cumsum(ids == bos_id)). This
9
+ is the structural fix for the v1 passkey=0 failure: v1 packed
10
+ multiple documents into one sequence and attended across the
11
+ BOS boundaries (model/llama.py:77 is_causal=True, no mask),
12
+ teaching the model to ignore long-range in-context signal.
13
+ See build_log A17/A18.
14
+ * qk_norm — per-head RMSNorm on Q and K before RoPE (Qwen3/OLMo-2/Gemma-3).
15
+ Caps attention-logit magnitude => training stability at higher
16
+ LR / more tokens.
17
+ * rope_fp32 — apply RoPE rotation in float32 then cast back. v1 ran the whole
18
+ model (and thus cos/sin) in bf16; bf16 rounding corrupts RoPE
19
+ relative positions at long context (AnchorAttention 2411.13476).
20
+ * z_loss_weight — auxiliary log(Z)^2 penalty on the LM logits (OLMo-2/PaLM); keeps
21
+ final logits from drifting large under bf16. Returned separately
22
+ for logging; folded into the training loss.
23
+
24
+ attn_impl selects the masking kernel:
25
+ * "sdpa" — F.scaled_dot_product_attention with an explicit boolean mask. Works on
26
+ CPU and CUDA, fine to moderate seq_len. Default; used by CPU smoke tests.
27
+ * "flex" — torch FlexAttention with a (causal AND same-document) mask_mod. Scales to
28
+ long context without materializing a (t,t) mask. CUDA path for the real run.
29
+
30
+ The model reuses RMSNorm / SwiGLU / build_rope_cache from model/llama.py.
31
+ """
32
+ from __future__ import annotations
33
+
34
+ import math
35
+
36
+ import torch
37
+ import torch.nn as nn
38
+ import torch.nn.functional as F
39
+
40
+ # --- inlined from model/llama.py (self-contained for HF trust_remote_code) ---
41
+ class RMSNorm(nn.Module):
42
+ def __init__(self, d: int, eps: float = 1e-5):
43
+ super().__init__()
44
+ self.eps = eps
45
+ self.weight = nn.Parameter(torch.ones(d))
46
+
47
+ def forward(self, x):
48
+ dtype = x.dtype
49
+ x = x.float()
50
+ x = x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + self.eps)
51
+ return (self.weight * x).to(dtype)
52
+
53
+
54
+ def build_rope_cache(head_dim: int, max_seq: int, theta: float, device, dtype=torch.float32):
55
+ freqs = 1.0 / (theta ** (torch.arange(0, head_dim, 2, device=device, dtype=dtype) / head_dim))
56
+ t = torch.arange(max_seq, device=device, dtype=dtype)
57
+ freqs = torch.outer(t, freqs) # (seq, head_dim/2)
58
+ return torch.cos(freqs), torch.sin(freqs)
59
+
60
+
61
+ class SwiGLU(nn.Module):
62
+ def __init__(self, cfg):
63
+ super().__init__()
64
+ self.w_gate = nn.Linear(cfg.d_model, cfg.d_ff, bias=False)
65
+ self.w_up = nn.Linear(cfg.d_model, cfg.d_ff, bias=False)
66
+ self.w_down = nn.Linear(cfg.d_ff, cfg.d_model, bias=False)
67
+
68
+ def forward(self, x):
69
+ return self.w_down(F.silu(self.w_gate(x)) * self.w_up(x))
70
+
71
+ BOS_ID = 1 # token id 1 is BOS in the MetaLLM tokenizer (see data/shard.py validate_shard)
72
+ FLEX_MIN_SEQ = 2048 # at/above this seq_len, "auto" uses FlexAttention (compact block mask)
73
+ # instead of the dense (b,1,t,t) SDPA mask, which would OOM at long ctx.
74
+
75
+ _FLEX_FN = None
76
+
77
+
78
+ def _flex_attention_fn():
79
+ """Lazily build a torch.compile'd flex_attention. Compiling is what gives flex its
80
+ memory win (otherwise it materializes the full score matrix — grok review A18)."""
81
+ global _FLEX_FN
82
+ if _FLEX_FN is None:
83
+ from torch.nn.attention.flex_attention import flex_attention
84
+ _FLEX_FN = torch.compile(flex_attention)
85
+ return _FLEX_FN
86
+
87
+
88
+ def resolve_impl(attn_impl: str, t: int, device_type: str) -> str:
89
+ """Pick the attention implementation for this forward. 'auto' uses flex on CUDA for
90
+ long sequences, sdpa otherwise. flex on CPU is unreliable, so CPU always uses sdpa."""
91
+ if device_type != "cuda":
92
+ return "sdpa"
93
+ if attn_impl == "flex":
94
+ return "flex"
95
+ if attn_impl == "auto":
96
+ return "flex" if t >= FLEX_MIN_SEQ else "sdpa"
97
+ return "sdpa"
98
+
99
+
100
+ # -------- rope (fp32-capable) --------
101
+
102
+ def apply_rope_v2(x, cos, sin, fp32: bool = True):
103
+ """Rotate x (b, n_heads, seq, head_dim). When fp32=True the rotation math runs
104
+ in float32 regardless of x.dtype, then casts back — avoids bf16 position drift."""
105
+ in_dtype = x.dtype
106
+ if fp32:
107
+ x = x.float()
108
+ cos = cos.float()
109
+ sin = sin.float()
110
+ d = x.shape[-1]
111
+ x1, x2 = x[..., : d // 2], x[..., d // 2:]
112
+ t = x.shape[-2]
113
+ cos = cos[:t].unsqueeze(0).unsqueeze(0)
114
+ sin = sin[:t].unsqueeze(0).unsqueeze(0)
115
+ out = torch.cat([x1 * cos - x2 * sin, x1 * sin + x2 * cos], dim=-1)
116
+ return out.to(in_dtype)
117
+
118
+
119
+ # -------- document-causal masking --------
120
+
121
+ def segment_ids_from_tokens(ids: torch.Tensor, bos_id: int = BOS_ID) -> torch.Tensor:
122
+ """Per-token document segment id. cumsum over BOS occurrences: every token gets
123
+ the index of the document it belongs to within the packed sequence. The leading
124
+ partial document (window started mid-doc, before any BOS) is segment 0 and attends
125
+ only to itself — correct."""
126
+ return (ids == bos_id).cumsum(dim=1)
127
+
128
+
129
+ def build_doc_causal_mask(seg: torch.Tensor) -> torch.Tensor:
130
+ """(b, 1, t, t) boolean mask, True = key participates. Allowed iff causal (j<=i)
131
+ AND same document (seg_i == seg_j)."""
132
+ b, t = seg.shape
133
+ causal = torch.tril(torch.ones(t, t, dtype=torch.bool, device=seg.device))
134
+ same_doc = seg.unsqueeze(2) == seg.unsqueeze(1) # (b, t, t)
135
+ return (same_doc & causal).unsqueeze(1) # (b, 1, t, t)
136
+
137
+
138
+ # -------- attention --------
139
+
140
+ class GroupedQueryAttentionV2(nn.Module):
141
+ def __init__(self, cfg, use_rope: bool = True):
142
+ super().__init__()
143
+ self.n_heads = cfg.n_heads
144
+ self.n_kv = cfg.n_kv_heads
145
+ self.head_dim = cfg.head_dim
146
+ self.scale = 1.0 / math.sqrt(self.head_dim)
147
+ self.rope_fp32 = getattr(cfg, "rope_fp32", True)
148
+ self.use_rope = use_rope # False => NoPE layer (full attn, no positional encoding)
149
+ self.wq = nn.Linear(cfg.d_model, cfg.n_heads * self.head_dim, bias=False)
150
+ self.wk = nn.Linear(cfg.d_model, cfg.n_kv_heads * self.head_dim, bias=False)
151
+ self.wv = nn.Linear(cfg.d_model, cfg.n_kv_heads * self.head_dim, bias=False)
152
+ self.wo = nn.Linear(cfg.n_heads * self.head_dim, cfg.d_model, bias=False)
153
+
154
+ self.qk_norm = getattr(cfg, "qk_norm", False)
155
+ if self.qk_norm:
156
+ self.q_norm = RMSNorm(self.head_dim, cfg.norm_eps)
157
+ self.k_norm = RMSNorm(self.head_dim, cfg.norm_eps)
158
+
159
+ def forward(self, x, cos, sin, attn_mask=None, block_mask=None):
160
+ b, t, _ = x.shape
161
+ q = self.wq(x).view(b, t, self.n_heads, self.head_dim).transpose(1, 2)
162
+ k = self.wk(x).view(b, t, self.n_kv, self.head_dim).transpose(1, 2)
163
+ v = self.wv(x).view(b, t, self.n_kv, self.head_dim).transpose(1, 2)
164
+
165
+ if self.qk_norm:
166
+ q = self.q_norm(q)
167
+ k = self.k_norm(k)
168
+
169
+ if self.use_rope:
170
+ q = apply_rope_v2(q, cos, sin, fp32=self.rope_fp32)
171
+ k = apply_rope_v2(k, cos, sin, fp32=self.rope_fp32)
172
+
173
+ rep = self.n_heads // self.n_kv
174
+ if rep > 1:
175
+ k = k.repeat_interleave(rep, dim=1)
176
+ v = v.repeat_interleave(rep, dim=1)
177
+
178
+ # The impl is chosen by which mask the model handed down: a flex BlockMask
179
+ # (long ctx, compact) or a dense SDPA boolean mask (short ctx) or neither (causal).
180
+ if block_mask is not None:
181
+ out = _flex_attention_fn()(q, k, v, block_mask=block_mask, scale=self.scale)
182
+ elif attn_mask is not None:
183
+ out = F.scaled_dot_product_attention(q, k, v, attn_mask=attn_mask, scale=self.scale)
184
+ else:
185
+ out = F.scaled_dot_product_attention(q, k, v, is_causal=True, scale=self.scale)
186
+
187
+ out = out.transpose(1, 2).contiguous().view(b, t, -1)
188
+ return self.wo(out)
189
+
190
+
191
+ # -------- block --------
192
+
193
+ class BlockV2(nn.Module):
194
+ def __init__(self, cfg, use_ckpt: bool = False, use_rope: bool = True):
195
+ super().__init__()
196
+ self.use_ckpt = use_ckpt
197
+ self.norm1 = RMSNorm(cfg.d_model, cfg.norm_eps)
198
+ self.attn = GroupedQueryAttentionV2(cfg, use_rope=use_rope)
199
+ self.norm2 = RMSNorm(cfg.d_model, cfg.norm_eps)
200
+ self.mlp = SwiGLU(cfg)
201
+
202
+ def _fwd(self, x, cos, sin, attn_mask, block_mask):
203
+ x = x + self.attn(self.norm1(x), cos, sin, attn_mask=attn_mask, block_mask=block_mask)
204
+ x = x + self.mlp(self.norm2(x))
205
+ return x
206
+
207
+ def forward(self, x, cos, sin, attn_mask=None, block_mask=None):
208
+ if self.use_ckpt and self.training:
209
+ return torch.utils.checkpoint.checkpoint(
210
+ self._fwd, x, cos, sin, attn_mask, block_mask, use_reentrant=False
211
+ )
212
+ return self._fwd(x, cos, sin, attn_mask, block_mask)
213
+
214
+
215
+ # -------- model --------
216
+
217
+ class MetaLLMv2(nn.Module):
218
+ def __init__(self, cfg, grad_ckpt: bool = False):
219
+ super().__init__()
220
+ self.cfg = cfg
221
+ self.grad_ckpt = grad_ckpt
222
+ self.doc_mask = getattr(cfg, "doc_mask", False)
223
+ self.attn_impl = getattr(cfg, "attn_impl", "sdpa")
224
+ self.z_loss_weight = getattr(cfg, "z_loss_weight", 0.0)
225
+ self.bos_id = getattr(cfg, "bos_id", BOS_ID)
226
+
227
+ self.tok_emb = nn.Embedding(cfg.vocab_size, cfg.d_model)
228
+ # NoPE: every `nope_every`-th layer drops RoPE (full-attn, no positional encoding) to grow
229
+ # dedicated retrieval heads (RNoPE / HyPE). nope_every=0 => all layers use RoPE (v2 behavior).
230
+ nope_every = getattr(cfg, "nope_every", 0)
231
+ def _use_rope(i: int) -> bool:
232
+ return not (nope_every and (i + 1) % nope_every == 0)
233
+ self.nope_layers = [i for i in range(cfg.n_layers) if not _use_rope(i)]
234
+ self.blocks = nn.ModuleList(
235
+ [BlockV2(cfg, use_ckpt=grad_ckpt, use_rope=_use_rope(i)) for i in range(cfg.n_layers)]
236
+ )
237
+ self.norm = RMSNorm(cfg.d_model, cfg.norm_eps)
238
+ self.lm_head = None if cfg.tie_embeddings else nn.Linear(cfg.d_model, cfg.vocab_size, bias=False)
239
+
240
+ # RoPE cache is built LAZILY in _rope() — registering it as a buffer breaks
241
+ # under transformers>=5.x meta-device loading (non-persistent buffers are
242
+ # materialized as uninitialized memory and never recomputed -> garbage/NaN).
243
+ self._rope_cache = None
244
+
245
+ self.apply(self._init)
246
+
247
+ @staticmethod
248
+ def _init(m):
249
+ if isinstance(m, nn.Linear):
250
+ nn.init.normal_(m.weight, mean=0.0, std=0.02)
251
+ if m.bias is not None:
252
+ nn.init.zeros_(m.bias)
253
+ elif isinstance(m, nn.Embedding):
254
+ nn.init.normal_(m.weight, mean=0.0, std=0.02)
255
+
256
+ def _make_masks(self, ids):
257
+ """Return (attn_mask, block_mask), or (None, None) when doc_mask off.
258
+ Resolves the impl per-forward: flex (compact block mask) for long ctx on CUDA,
259
+ dense SDPA bool mask for short ctx, so the long-context curriculum phase doesn't
260
+ OOM on a (b,1,t,t) mask (grok review A18)."""
261
+ if not self.doc_mask:
262
+ return None, None
263
+ b, t = ids.shape
264
+ seg = segment_ids_from_tokens(ids, self.bos_id)
265
+ impl = resolve_impl(self.attn_impl, t, ids.device.type)
266
+ if impl == "flex":
267
+ from torch.nn.attention.flex_attention import create_block_mask
268
+
269
+ def mask_mod(bi, hi, qi, ki):
270
+ return (qi >= ki) & (seg[bi, qi] == seg[bi, ki])
271
+
272
+ block_mask = create_block_mask(mask_mod, B=b, H=None, Q_LEN=t, KV_LEN=t, device=ids.device)
273
+ return None, block_mask
274
+ return build_doc_causal_mask(seg), None
275
+
276
+ def _rope(self, device):
277
+ # Fidelity: the native model registers cos/sin as buffers, so .to(bfloat16)
278
+ # rounds them to the model dtype — the network trained against THOSE values.
279
+ # Build fp32 then round through the param dtype (2026-07-10: pristine-fp32
280
+ # cache caused compounding layer drift, 4% greedy argmax flips, ~2x pass@1
281
+ # loss vs native; see build_log.md).
282
+ dtype = self.tok_emb.weight.dtype
283
+ if (self._rope_cache is None or self._rope_cache[0].device != device
284
+ or self._rope_cache[0].dtype != dtype):
285
+ cos, sin = build_rope_cache(self.cfg.head_dim, self.cfg.max_seq_len,
286
+ self.cfg.rope_theta, device=device)
287
+ self._rope_cache = (cos.to(dtype), sin.to(dtype))
288
+ return self._rope_cache
289
+
290
+ def forward(self, ids, targets=None):
291
+ x = self.tok_emb(ids)
292
+ cos, sin = self._rope(x.device)
293
+ attn_mask, block_mask = self._make_masks(ids)
294
+
295
+ for blk in self.blocks:
296
+ x = blk(x, cos, sin, attn_mask=attn_mask, block_mask=block_mask)
297
+ x = self.norm(x)
298
+
299
+ if targets is None:
300
+ logits = F.linear(x, self.tok_emb.weight) if self.lm_head is None else self.lm_head(x)
301
+ return logits, None
302
+
303
+ # Training path: fused, chunked head+loss. The dense [B,T,vocab] logits (plus the fp32
304
+ # upcast the z-loss needs) are the dominant activation cost at large vocab/seq and force
305
+ # tiny batches. Instead project + score in row-chunks, checkpointing each chunk so its
306
+ # logits are recomputed in backward rather than retained. Identical math to the dense
307
+ # path (only fp summation order differs). Training callers read only the loss, so logits
308
+ # are returned as None.
309
+ ce, z_loss = self._chunked_loss(x, targets)
310
+ loss = ce + z_loss
311
+ self._last_ce = ce.detach()
312
+ self._last_zloss = z_loss.detach()
313
+ return None, loss
314
+
315
+ def _chunked_loss(self, x, targets, chunk: int = 2048):
316
+ """Memory-bounded CE + z-loss. Peak head memory ~chunk*vocab regardless of B*T, so
317
+ large batches fit. Exact: each chunk is checkpointed (logits recomputed in backward)."""
318
+ W = self.tok_emb.weight if self.lm_head is None else self.lm_head.weight
319
+ xf = x.reshape(-1, x.size(-1))
320
+ tf = targets.reshape(-1)
321
+ N = xf.size(0)
322
+ zw = float(self.z_loss_weight)
323
+
324
+ def chunk_fn(xc, tc):
325
+ lc = F.linear(xc, W)
326
+ ce_sum = F.cross_entropy(lc, tc, ignore_index=-100, reduction="sum")
327
+ if zw > 0.0:
328
+ m = tc != -100
329
+ z_sum = torch.logsumexp(lc[m].float(), dim=-1).pow(2).sum() if m.any() \
330
+ else lc.new_zeros(())
331
+ else:
332
+ z_sum = lc.new_zeros(())
333
+ return ce_sum, z_sum
334
+
335
+ ce_total = xf.new_zeros(())
336
+ z_total = xf.new_zeros(())
337
+ for i in range(0, N, chunk):
338
+ xc, tc = xf[i:i + chunk], tf[i:i + chunk]
339
+ if self.training and xc.requires_grad:
340
+ ce_sum, z_sum = torch.utils.checkpoint.checkpoint(
341
+ chunk_fn, xc, tc, use_reentrant=False)
342
+ else:
343
+ ce_sum, z_sum = chunk_fn(xc, tc)
344
+ ce_total = ce_total + ce_sum
345
+ z_total = z_total + z_sum
346
+ n_valid = (tf != -100).sum().clamp(min=1)
347
+ ce = ce_total / n_valid
348
+ z_loss = (zw * z_total / n_valid) if zw > 0.0 else xf.new_zeros(())
349
+ return ce, z_loss
350
+
351
+ def encode(self, ids, pad_id: int = 0, bidirectional: bool = True):
352
+ """Pooled sentence embedding (b, d) for the Track B embedding model.
353
+ bidirectional=True runs full (non-causal) attention over the real tokens
354
+ (LLM2Vec-style — better embedding geometry than causal); pooling is masked
355
+ mean over non-pad positions. Returns grad-enabled embeddings for contrastive FT."""
356
+ x = self.tok_emb(ids)
357
+ cos, sin = self._rope(x.device)
358
+ nonpad = (ids != pad_id) # (b, t)
359
+ b, t = ids.shape
360
+ if bidirectional:
361
+ attn_mask = nonpad[:, None, None, :].expand(b, 1, t, t) # attend to all real keys
362
+ else:
363
+ attn_mask, _ = self._make_masks(ids)
364
+ for blk in self.blocks:
365
+ x = blk(x, cos, sin, attn_mask=attn_mask, block_mask=None)
366
+ x = self.norm(x)
367
+ m = nonpad.unsqueeze(-1).to(x.dtype)
368
+ return (x * m).sum(1) / m.sum(1).clamp(min=1.0)
369
+
370
+ def num_params(self, non_embedding: bool = False) -> int:
371
+ n = sum(p.numel() for p in self.parameters())
372
+ if non_embedding:
373
+ n -= self.tok_emb.weight.numel()
374
+ return n
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c754911b6ba65a98e1ed137488228d1613563c8db419a46046d398c1cd57988f
3
+ size 2004481000
modeling_metallm.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """HF modeling shim for MetaLLM / Vishvakarma.
2
+
3
+ Wraps the native MetaLLMv2 (metallm_core.py, vendored + self-contained) in a
4
+ PreTrainedModel so the checkpoint is `AutoModelForCausalLM.from_pretrained(...,
5
+ trust_remote_code=True)`-loadable — the exact NoPE/QK-norm architecture, not a
6
+ lossy Llama-config approximation.
7
+
8
+ v1 scope: no KV cache (generation recomputes the prefix each step — correct, just
9
+ slower). Fine for demos/evals; a cached path is future work.
10
+ """
11
+ import torch
12
+ import torch.nn.functional as F
13
+ from transformers import PreTrainedModel
14
+ from transformers.modeling_outputs import CausalLMOutputWithPast
15
+
16
+ from .configuration_metallm import MetaLLMConfig
17
+ from .metallm_core import MetaLLMv2
18
+
19
+
20
+ class MetaLLMForCausalLM(PreTrainedModel):
21
+ config_class = MetaLLMConfig
22
+ base_model_prefix = "model"
23
+ supports_gradient_checkpointing = False
24
+ _no_split_modules = ["BlockV2"]
25
+ # weights are shared inside MetaLLMv2 (lm_head is None when tied) — nothing for
26
+ # HF to re-tie. transformers>=5.13 requires all_tied_weights_keys to exist.
27
+ _tied_weights_keys = []
28
+
29
+ @property
30
+ def all_tied_weights_keys(self):
31
+ return {}
32
+
33
+ def __init__(self, config: MetaLLMConfig):
34
+ super().__init__(config)
35
+ self.model = MetaLLMv2(config)
36
+ # post_init would re-run HF default init over our scaled init; the weights
37
+ # come from the checkpoint anyway, so skip it deliberately.
38
+
39
+ # --- embeddings plumbing (needed by resize/tie utilities) ---
40
+ def get_input_embeddings(self):
41
+ return self.model.tok_emb
42
+
43
+ def set_input_embeddings(self, value):
44
+ self.model.tok_emb = value
45
+
46
+ def get_output_embeddings(self):
47
+ return self.model.lm_head # None when tied — HF treats tied as expected
48
+
49
+ def forward(
50
+ self,
51
+ input_ids: torch.LongTensor,
52
+ attention_mask: torch.Tensor = None, # accepted for API compat; causal-only v1
53
+ labels: torch.LongTensor = None,
54
+ past_key_values=None,
55
+ use_cache: bool = None,
56
+ **kwargs,
57
+ ) -> CausalLMOutputWithPast:
58
+ logits, _ = self.model(input_ids) # native path, targets=None -> (logits, None)
59
+ loss = None
60
+ if labels is not None:
61
+ shift_logits = logits[:, :-1, :].contiguous()
62
+ shift_labels = labels[:, 1:].contiguous()
63
+ loss = F.cross_entropy(
64
+ shift_logits.view(-1, shift_logits.size(-1)),
65
+ shift_labels.view(-1),
66
+ ignore_index=-100,
67
+ )
68
+ return CausalLMOutputWithPast(loss=loss, logits=logits, past_key_values=None)
69
+
70
+ def prepare_inputs_for_generation(self, input_ids, **kwargs):
71
+ # No KV cache: feed the full sequence every step.
72
+ return {"input_ids": input_ids}
serve_metallum.py ADDED
@@ -0,0 +1,615 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Local serving wrapper for the exported MetaLLM s2b075 HF bundle.
3
+
4
+ Product answer to the measured format-acquisition failure: strict structured
5
+ output is enforced AT DECODE TIME (token-level grammar/schema constraints),
6
+ not hoped for from the weights. SFT got 0/60 on the frozen strict-format
7
+ suite; this wrapper guarantees syntactic validity and schema conformance on
8
+ every request by construction.
9
+
10
+ Modes (POST /generate):
11
+ free — plain continuation (greedy or temperature-sampled, seeded)
12
+ json — flat-object schema forced: exact keys, typed values
13
+ tool — harness contract: {"thought","tool","args"} or {"thought","final"}
14
+ mcq — single letter A-D then EOS
15
+
16
+ Binds 127.0.0.1 only. No auth, no network exposure: localhost product shim.
17
+ """
18
+ from __future__ import annotations
19
+
20
+ import argparse
21
+ import json
22
+ import re
23
+ import sys
24
+ import threading
25
+ import time
26
+ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
27
+ from pathlib import Path
28
+
29
+ import torch
30
+
31
+ BUNDLE = Path(__file__).resolve().parent # model files ship beside this script
32
+ TOOLS = ["python_exec", "write_file", "read_file", "ls"]
33
+ MAX_CTX = 2048
34
+ BOS, EOS = 1, 2
35
+
36
+
37
+ # --- strict whole-output validators (self-contained copies) -----------------
38
+ def check_json(text: str, schema: dict) -> bool:
39
+ s = text.strip()
40
+ if s.startswith("```"):
41
+ s = s.strip("`").removeprefix("json").strip()
42
+ try:
43
+ obj = json.loads(s)
44
+ except Exception:
45
+ return False
46
+ if not isinstance(obj, dict):
47
+ return False
48
+ for k, t in schema.items():
49
+ if k not in obj or not isinstance(obj[k], t):
50
+ return False
51
+ return True
52
+
53
+
54
+ def check_tool(text: str) -> bool:
55
+ s = text.strip()
56
+ try:
57
+ obj = json.loads(s)
58
+ except Exception:
59
+ return False
60
+ if not isinstance(obj, dict) or "thought" not in obj:
61
+ return False
62
+ if "final" in obj:
63
+ return True
64
+ return obj.get("tool") in TOOLS and isinstance(obj.get("args"), dict)
65
+
66
+
67
+ def check_mcq(text: str) -> bool:
68
+ t = text.strip().rstrip(".").strip()
69
+ return len(t) == 1 and t in "ABCD"
70
+
71
+ # ---------------------------------------------------------------------------
72
+ # generic JSON prefix state machine (for free-form values such as tool args)
73
+ # ---------------------------------------------------------------------------
74
+
75
+ _INT_PART = re.compile(r"^-?(0|[1-9][0-9]*)?$")
76
+ _INT_FULL = re.compile(r"^-?(0|[1-9][0-9]*)$")
77
+ _NUM_PART = re.compile(r"^-?(0|[1-9][0-9]*)?(\.[0-9]*)?([eE][+-]?[0-9]*)?$")
78
+ _NUM_FULL = re.compile(r"^-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?$")
79
+ _LITS = ("true", "false", "null")
80
+ _WS = " \t\n\r"
81
+ _NUM_CHARS = set("0123456789.eE+-")
82
+ _LIT_CHARS = set("abcdefghijklmnopqrstuvwxyz")
83
+
84
+
85
+ class JsonError(Exception):
86
+ pass
87
+
88
+
89
+ class JsonSM:
90
+ """Incremental validator for a single top-level JSON value."""
91
+
92
+ def __init__(self, depth_cap: int = 8):
93
+ self.stack: list[str] = [] # 'obj' | 'arr'
94
+ self.mode = "value" # value|key|key_or_end|colon|comma|string|number|lit|end
95
+ self.buf = "" # partial number / literal
96
+ self.esc = False
97
+ self.hex_left = 0
98
+ self.after_string = "comma" # where to go when a string closes
99
+ self.depth_cap = depth_cap
100
+
101
+ def copy(self) -> "JsonSM":
102
+ s = JsonSM(self.depth_cap)
103
+ s.stack = list(self.stack)
104
+ s.mode, s.buf, s.esc, s.hex_left, s.after_string = (
105
+ self.mode, self.buf, self.esc, self.hex_left, self.after_string)
106
+ return s
107
+
108
+ # -- internal helpers --------------------------------------------------
109
+ def _value_done(self):
110
+ if not self.stack:
111
+ self.mode = "end"
112
+ else:
113
+ self.mode = "comma"
114
+
115
+ def _feed(self, ch: str):
116
+ m = self.mode
117
+ if m == "end":
118
+ if ch not in _WS:
119
+ raise JsonError("trailing content")
120
+ return
121
+ if m == "string":
122
+ if self.hex_left:
123
+ if ch not in "0123456789abcdefABCDEF":
124
+ raise JsonError("bad unicode escape")
125
+ self.hex_left -= 1
126
+ return
127
+ if self.esc:
128
+ if ch == "u":
129
+ self.hex_left = 4
130
+ elif ch not in '"\\/bfnrt':
131
+ raise JsonError("bad escape")
132
+ self.esc = False
133
+ return
134
+ if ch == "\\":
135
+ self.esc = True
136
+ elif ch == '"':
137
+ self.mode = self.after_string
138
+ elif ord(ch) < 0x20:
139
+ raise JsonError("control char in string")
140
+ return
141
+ if m == "number":
142
+ if ch in _NUM_CHARS:
143
+ if not _NUM_PART.match(self.buf + ch):
144
+ raise JsonError("bad number")
145
+ self.buf += ch
146
+ return
147
+ if not _NUM_FULL.match(self.buf):
148
+ raise JsonError("incomplete number")
149
+ self.buf = ""
150
+ self._value_done()
151
+ self._feed(ch) # re-feed delimiter
152
+ return
153
+ if m == "lit":
154
+ if ch in _LIT_CHARS:
155
+ self.buf += ch
156
+ if not any(w.startswith(self.buf) for w in _LITS):
157
+ raise JsonError("bad literal")
158
+ if self.buf in _LITS:
159
+ self.buf = ""
160
+ self._value_done()
161
+ return
162
+ if self.buf: # incomplete literal followed by delimiter
163
+ raise JsonError("incomplete literal")
164
+ self._feed(ch)
165
+ return
166
+ # structural modes
167
+ if ch in _WS:
168
+ return
169
+ if m == "value":
170
+ if ch == "{":
171
+ if len(self.stack) >= self.depth_cap:
172
+ raise JsonError("depth cap")
173
+ self.stack.append("obj")
174
+ self.mode = "key_or_end"
175
+ elif ch == "[":
176
+ if len(self.stack) >= self.depth_cap:
177
+ raise JsonError("depth cap")
178
+ self.stack.append("arr")
179
+ self.mode = "value_or_end"
180
+ elif ch == '"':
181
+ self.after_string = "comma" if self.stack else "end"
182
+ self.mode = "string"
183
+ elif ch in "-0123456789":
184
+ self.mode = "number"
185
+ self.buf = ch
186
+ elif ch in "tfn":
187
+ self.mode = "lit"
188
+ self.buf = ch
189
+ else:
190
+ raise JsonError("expected value")
191
+ return
192
+ if m == "value_or_end": # just after '['
193
+ if ch == "]":
194
+ self.stack.pop()
195
+ self._value_done()
196
+ return
197
+ self.mode = "value"
198
+ self._feed(ch)
199
+ return
200
+ if m == "key_or_end": # just after '{' — empty object may close
201
+ if ch == "}":
202
+ self.stack.pop()
203
+ self._value_done()
204
+ return
205
+ self.mode = "key"
206
+ self._feed(ch)
207
+ return
208
+ if m == "key":
209
+ if ch == '"':
210
+ self.after_string = "colon"
211
+ self.mode = "string"
212
+ return
213
+ raise JsonError("expected key")
214
+ if m == "colon":
215
+ if ch == ":":
216
+ self.mode = "value"
217
+ return
218
+ raise JsonError("expected colon")
219
+ if m == "comma":
220
+ top = self.stack[-1] if self.stack else None
221
+ if top == "obj":
222
+ if ch == ",":
223
+ self.mode = "key"
224
+ return
225
+ if ch == "}":
226
+ self.stack.pop()
227
+ self._value_done()
228
+ return
229
+ elif top == "arr":
230
+ if ch == ",":
231
+ self.mode = "value"
232
+ return
233
+ if ch == "]":
234
+ self.stack.pop()
235
+ self._value_done()
236
+ return
237
+ raise JsonError("expected comma/close")
238
+ raise JsonError(f"bad mode {m}")
239
+
240
+ def feed(self, text: str):
241
+ for ch in text:
242
+ self._feed(ch)
243
+
244
+ def allows(self, text: str) -> bool:
245
+ try:
246
+ s = self.copy()
247
+ s.feed(text)
248
+ return True
249
+ except JsonError:
250
+ return False
251
+
252
+ @property
253
+ def complete(self) -> bool:
254
+ return self.mode == "end"
255
+
256
+
257
+ # ---------------------------------------------------------------------------
258
+ # constrained generator
259
+ # ---------------------------------------------------------------------------
260
+
261
+ class ConstrainedDecoder:
262
+ def __init__(self, model, tok, device: str):
263
+ self.model = model
264
+ self.tok = tok
265
+ self.device = device
266
+ self.lock = threading.Lock()
267
+ # vocab text cache
268
+ n = 40000
269
+ self.vtext: list[str] = [tok.decode([i]) for i in range(n)]
270
+ # token classes (special ids 0=pad,1=bos,2=eos excluded everywhere)
271
+ self.quote_ids = [i for i, t in enumerate(self.vtext) if t == '"' and i > 2]
272
+ self.closer_ids = [i for i, t in enumerate(self.vtext)
273
+ if i > 2 and t and any(c in '}]"' for c in t)
274
+ and all(c in '}]" \t\n\r' for c in t)]
275
+ self.num_cand_ids = [i for i, t in enumerate(self.vtext)
276
+ if i > 2 and t and all(c in _NUM_CHARS for c in t)]
277
+ self.str_cand_ids = [i for i, t in enumerate(self.vtext)
278
+ if i > 2 and t and '"' not in t and "\\" not in t
279
+ and all(ord(c) >= 0x20 for c in t)]
280
+ self.mcq_ids = [i for i, t in enumerate(self.vtext)
281
+ if i > 2 and t in ("A", "B", "C", "D", " A", " B", " C", " D")]
282
+ self._closer_set = set(self.closer_ids)
283
+ assert self.quote_ids and self.num_cand_ids and self.str_cand_ids and self.mcq_ids
284
+
285
+ # -- primitives ---------------------------------------------------------
286
+ def encode_lit(self, text: str) -> list[int]:
287
+ ids = self.tok.encode(text, add_special_tokens=False)
288
+ if self.tok.decode(ids) == text:
289
+ return ids
290
+ out = []
291
+ for ch in text: # fall back to per-char forcing (roundtrip-safe)
292
+ ci = self.tok.encode(ch, add_special_tokens=False)
293
+ if self.tok.decode(ci) != ch:
294
+ raise RuntimeError(f"cannot force {text!r}")
295
+ out.extend(ci)
296
+ return out
297
+
298
+ @torch.no_grad()
299
+ def _logits(self, ids: list[int]) -> torch.Tensor:
300
+ x = torch.tensor([ids[-MAX_CTX:]], dtype=torch.long, device=self.device)
301
+ return self.model(input_ids=x).logits[0, -1].float()
302
+
303
+ @staticmethod
304
+ def _pick(logits: torch.Tensor, cand: list[int], gen: torch.Generator | None,
305
+ temperature: float) -> int:
306
+ t = logits[cand]
307
+ if gen is None or temperature <= 0:
308
+ return cand[int(t.argmax().item())]
309
+ p = torch.softmax(t / max(temperature, 1e-5), dim=-1)
310
+ return cand[int(torch.multinomial(p, 1, generator=gen).item())]
311
+
312
+ def _pick_with_stop(self, logits, cand, stop_id, gen, temperature):
313
+ """Choose among cand tokens plus a virtual STOP action (stop_id's logit)."""
314
+ ids = cand + [stop_id]
315
+ choice = self._pick(logits, ids, gen, temperature)
316
+ return None if choice == stop_id else choice
317
+
318
+ # -- typed values --------------------------------------------------------
319
+ def gen_number(self, ids, kind: str, next_struct: int, gen, temperature,
320
+ budget: list[int]) -> str:
321
+ part, full = (_INT_PART, _INT_FULL) if kind == "int" else (_NUM_PART, _NUM_FULL)
322
+ so_far = ""
323
+ while budget[0] > 0 and len(so_far) < 18:
324
+ cand = [i for i in self.num_cand_ids if part.match(so_far + self.vtext[i])]
325
+ if not cand:
326
+ break
327
+ logits = self._logits(ids)
328
+ if full.match(so_far):
329
+ nxt = self._pick_with_stop(logits, cand, next_struct, gen, temperature)
330
+ if nxt is None:
331
+ break
332
+ else:
333
+ nxt = self._pick(logits, cand, gen, temperature)
334
+ ids.append(nxt)
335
+ budget[0] -= 1
336
+ so_far += self.vtext[nxt]
337
+ if not full.match(so_far):
338
+ raise RuntimeError(f"number underflow: {so_far!r}")
339
+ return so_far
340
+
341
+ def gen_string(self, ids, gen, temperature, budget: list[int],
342
+ max_chars: int = 160) -> str:
343
+ # Graceful close at max_chars/budget: the model is never required to
344
+ # prefer the bare-quote token, so strings are capped, never fatal.
345
+ out = ""
346
+ while budget[0] > 0 and len(out) < max_chars:
347
+ logits = self._logits(ids)
348
+ cand = self.str_cand_ids + self.quote_ids
349
+ nxt = self._pick(logits, cand, gen, temperature)
350
+ ids.append(nxt)
351
+ budget[0] -= 1
352
+ if nxt in self.quote_ids:
353
+ return out
354
+ out += self.vtext[nxt]
355
+ return out # caller appends the closing quote
356
+
357
+ def gen_bool(self, ids, gen, temperature, budget: list[int]) -> str:
358
+ t_ids = self.encode_lit("true")
359
+ f_ids = self.encode_lit("false")
360
+ logits = self._logits(ids)
361
+ first = self._pick(logits, [t_ids[0], f_ids[0]], gen, temperature)
362
+ seq = t_ids if first == t_ids[0] else f_ids
363
+ for tid in seq:
364
+ ids.append(tid)
365
+ budget[0] -= 1
366
+ return "true" if seq is t_ids else "false"
367
+
368
+ def gen_json_free(self, ids, gen, temperature, budget: list[int],
369
+ sm: JsonSM | None = None, require_container: bool = False) -> str:
370
+ sm = sm or JsonSM()
371
+ out = ""
372
+ mask_cache: dict = {}
373
+ while budget[0] > 0 and not sm.complete:
374
+ key = (sm.mode, tuple(sm.stack), sm.buf, sm.esc, sm.hex_left, sm.after_string)
375
+ cand = mask_cache.get(key)
376
+ if cand is None:
377
+ cand = [i for i, t in enumerate(self.vtext) if t and sm.allows(t)]
378
+ mask_cache[key] = cand
379
+ if require_container and not out.strip():
380
+ cand = [i for i in cand
381
+ if self.vtext[i].strip() and self.vtext[i].lstrip()[:1] in "{["]
382
+ if budget[0] <= 24: # force-close: steer to completion within reserve
383
+ closing = [i for i in cand if i in self._closer_set]
384
+ if closing:
385
+ cand = closing
386
+ if not cand:
387
+ raise RuntimeError("json mask exhausted")
388
+ logits = self._logits(ids)
389
+ nxt = self._pick(logits, cand, gen, temperature)
390
+ ids.append(nxt)
391
+ budget[0] -= 1
392
+ txt = self.vtext[nxt]
393
+ sm.feed(txt)
394
+ out += txt
395
+ if not sm.complete:
396
+ raise RuntimeError("json did not complete")
397
+ return out
398
+
399
+ # -- top-level modes -----------------------------------------------------
400
+ def generate(self, prompt: str, mode: str = "free", schema: dict | None = None,
401
+ max_new: int = 160, temperature: float = 0.0,
402
+ seed: int | None = None) -> dict:
403
+ with self.lock:
404
+ return self._generate(prompt, mode, schema, max_new, temperature, seed)
405
+
406
+ def _generate(self, prompt, mode, schema, max_new, temperature, seed):
407
+ gen = None
408
+ if seed is not None:
409
+ gen = torch.Generator(device=self.device).manual_seed(seed)
410
+ ids = [BOS] + self.tok.encode(prompt, add_special_tokens=False)
411
+ n_prompt = len(ids)
412
+ if n_prompt + max_new > MAX_CTX:
413
+ raise ValueError(f"prompt({n_prompt})+max_new({max_new}) exceeds {MAX_CTX}")
414
+ budget = [max_new]
415
+ text = ""
416
+ t0 = time.time()
417
+
418
+ if mode == "free":
419
+ while budget[0] > 0:
420
+ logits = self._logits(ids)
421
+ if gen is None or temperature <= 0:
422
+ nxt = int(logits.argmax().item())
423
+ else:
424
+ p = torch.softmax(logits / temperature, dim=-1)
425
+ nxt = int(torch.multinomial(p, 1, generator=gen).item())
426
+ if nxt == EOS:
427
+ break
428
+ ids.append(nxt)
429
+ budget[0] -= 1
430
+ text += self.vtext[nxt]
431
+
432
+ elif mode == "json":
433
+ schema = schema or {}
434
+ items = list(schema.items())
435
+ if not items: # schema-less: free-form JSON object/array, grammar-masked
436
+ text += self.gen_json_free(ids, gen, temperature, budget, JsonSM(),
437
+ require_container=True)
438
+ return self._finish(ids, n_prompt, t0, text, mode)
439
+ for tid in self.encode_lit("{"):
440
+ ids.append(tid); budget[0] -= 1
441
+ text += "{"
442
+ for i, (key, vtype) in enumerate(items):
443
+ lead = '"' if i == 0 else ', "'
444
+ lit = f'{lead}{key}": '
445
+ for tid in self.encode_lit(lit):
446
+ ids.append(tid); budget[0] -= 1
447
+ text += lit
448
+ # structural token used as the STOP comparator
449
+ nxt_lit = "}" if i == len(items) - 1 else f', "{items[i+1][0]}": '
450
+ stop_id = self.encode_lit(nxt_lit)[0]
451
+ if vtype == "string":
452
+ for tid in self.encode_lit('"'):
453
+ ids.append(tid); budget[0] -= 1
454
+ text += '"'
455
+ val_cap = max(0, min(160, (budget[0] - 8) * 4))
456
+ text += self.gen_string(ids, gen, temperature, budget, val_cap) + '"'
457
+ elif vtype in ("int", "number"):
458
+ text += self.gen_number(ids, vtype, stop_id, gen, temperature, budget)
459
+ elif vtype == "bool":
460
+ text += self.gen_bool(ids, gen, temperature, budget)
461
+ else:
462
+ raise ValueError(f"unsupported schema type {vtype!r}")
463
+ for tid in self.encode_lit("}"):
464
+ ids.append(tid); budget[0] -= 1
465
+ text += "}"
466
+
467
+ elif mode == "tool":
468
+ for tid in self.encode_lit('{"thought": "'):
469
+ ids.append(tid); budget[0] -= 1
470
+ text += '{"thought": "'
471
+ thought_cap = max(0, min(120, (budget[0] - 40) * 4))
472
+ text += self.gen_string(ids, gen, temperature, budget, thought_cap) + '"'
473
+ tool_lit, final_lit = ', "tool": "', ', "final": "'
474
+ tl, fl = self.encode_lit(tool_lit), self.encode_lit(final_lit)
475
+ logits = self._logits(ids)
476
+ branch = self._pick(logits, [tl[0], fl[0]], gen, temperature)
477
+ if branch == fl[0]:
478
+ for tid in fl:
479
+ ids.append(tid); budget[0] -= 1
480
+ text += final_lit
481
+ final_cap = max(0, min(600, (budget[0] - 4) * 4))
482
+ text += self.gen_string(ids, gen, temperature, budget, final_cap) + '"'
483
+ else:
484
+ for tid in tl:
485
+ ids.append(tid); budget[0] -= 1
486
+ text += tool_lit
487
+ name_lits = {name: self.encode_lit(name) for name in TOOLS}
488
+ logits = self._logits(ids)
489
+ first = self._pick(logits, [nl[0] for nl in name_lits.values()],
490
+ gen, temperature)
491
+ chosen = next(n for n, nl in name_lits.items() if nl[0] == first)
492
+ for tid in name_lits[chosen]:
493
+ ids.append(tid); budget[0] -= 1
494
+ text += chosen
495
+ mid = '", "args": '
496
+ for tid in self.encode_lit(mid):
497
+ ids.append(tid); budget[0] -= 1
498
+ text += mid
499
+ sm = JsonSM()
500
+ sm.feed("{")
501
+ ids.extend(self.encode_lit("{")); budget[0] -= 1
502
+ text += "{"
503
+ text += self.gen_json_free(ids, gen, temperature, budget, sm)
504
+ for tid in self.encode_lit("}"):
505
+ ids.append(tid); budget[0] -= 1
506
+ text += "}"
507
+
508
+ elif mode == "mcq":
509
+ logits = self._logits(ids)
510
+ nxt = self._pick(logits, self.mcq_ids, gen, temperature)
511
+ ids.append(nxt)
512
+ budget[0] -= 1
513
+ text += self.vtext[nxt]
514
+
515
+ else:
516
+ raise ValueError(f"unknown mode {mode!r}")
517
+
518
+ return self._finish(ids, n_prompt, t0, text, mode)
519
+
520
+ @staticmethod
521
+ def _finish(ids, n_prompt, t0, text, mode):
522
+ dt = time.time() - t0
523
+ return {
524
+ "text": text, "mode": mode,
525
+ "usage": {"prompt_tokens": n_prompt, "gen_tokens": len(ids) - n_prompt,
526
+ "seconds": round(dt, 2)},
527
+ }
528
+
529
+
530
+ # ---------------------------------------------------------------------------
531
+ # HTTP layer
532
+ # ---------------------------------------------------------------------------
533
+
534
+ def validate(mode: str, text: str, schema: dict | None) -> bool:
535
+ if mode == "json" and schema:
536
+ pysch = {k: {"int": int, "number": (int, float), "string": str,
537
+ "bool": bool}[t] for k, t in schema.items()}
538
+ return check_json(text, pysch)
539
+ if mode == "json":
540
+ try:
541
+ return isinstance(json.loads(text), (dict, list))
542
+ except Exception:
543
+ return False
544
+ if mode == "tool":
545
+ return check_tool(text)
546
+ if mode == "mcq":
547
+ return check_mcq(text)
548
+ return True
549
+
550
+
551
+ def make_handler(dec: ConstrainedDecoder):
552
+ class H(BaseHTTPRequestHandler):
553
+ def _send(self, code: int, obj: dict):
554
+ body = json.dumps(obj).encode()
555
+ self.send_response(code)
556
+ self.send_header("Content-Type", "application/json")
557
+ self.send_header("Content-Length", str(len(body)))
558
+ self.end_headers()
559
+ self.wfile.write(body)
560
+
561
+ def log_message(self, fmt, *a): # quieter logs
562
+ sys.stderr.write("[serve] " + fmt % a + "\n")
563
+
564
+ def do_GET(self):
565
+ if self.path == "/health":
566
+ self._send(200, {"status": "ok", "bundle": str(BUNDLE),
567
+ "modes": ["free", "json", "tool", "mcq"]})
568
+ else:
569
+ self._send(404, {"error": "unknown path"})
570
+
571
+ def do_POST(self):
572
+ if self.path != "/generate":
573
+ self._send(404, {"error": "unknown path"})
574
+ return
575
+ try:
576
+ req = json.loads(self.rfile.read(int(self.headers["Content-Length"])))
577
+ out = dec.generate(
578
+ prompt=req["prompt"],
579
+ mode=req.get("mode", "free"),
580
+ schema=req.get("schema"),
581
+ max_new=int(req.get("max_new", 160)),
582
+ temperature=float(req.get("temperature", 0.0)),
583
+ seed=req.get("seed"),
584
+ )
585
+ out["valid"] = validate(out["mode"], out["text"], req.get("schema"))
586
+ self._send(200, out)
587
+ except Exception as e: # fail loudly, keep serving
588
+ self._send(400, {"error": f"{type(e).__name__}: {e}"})
589
+
590
+ return H
591
+
592
+
593
+ def main():
594
+ ap = argparse.ArgumentParser()
595
+ ap.add_argument("--port", type=int, default=8399)
596
+ ap.add_argument("--device", default="cuda:0")
597
+ args = ap.parse_args()
598
+
599
+ from transformers import AutoModelForCausalLM, AutoTokenizer
600
+ tok = AutoTokenizer.from_pretrained(str(BUNDLE))
601
+ model = AutoModelForCausalLM.from_pretrained(
602
+ str(BUNDLE), trust_remote_code=True, torch_dtype=torch.bfloat16)
603
+ model = model.to(args.device).eval()
604
+ n_params = sum(p.numel() for p in model.parameters())
605
+ print(f"[serve] loaded {type(model).__name__} {n_params/1e6:.1f}M on {args.device}",
606
+ flush=True)
607
+
608
+ dec = ConstrainedDecoder(model, tok, args.device)
609
+ srv = ThreadingHTTPServer(("127.0.0.1", args.port), make_handler(dec))
610
+ print(f"[serve] listening on http://127.0.0.1:{args.port} (localhost only)", flush=True)
611
+ srv.serve_forever()
612
+
613
+
614
+ if __name__ == "__main__":
615
+ main()
tokenizer.json ADDED
The diff for this file is too large to render. See raw diff
 
tokenizer_config.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "tokenizer_class": "PreTrainedTokenizerFast",
3
+ "bos_token": "<s>",
4
+ "eos_token": "</s>",
5
+ "pad_token": "<pad>",
6
+ "model_input_names": [
7
+ "input_ids",
8
+ "attention_mask"
9
+ ]
10
+ }