blt-trainer 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,8 @@
1
+ Metadata-Version: 2.5
2
+ Name: blt-trainer
3
+ Version: 0.1.0
4
+ Summary: Pure functional 96-layer Byte Latent Transformer trainer with Muon and weighted streaming datamix
5
+ Requires-Python: >=3.10
6
+ Requires-Dist: datasets>=2.16.0
7
+ Requires-Dist: numpy>=1.24.0
8
+ Requires-Dist: torch>=2.2.0
File without changes
@@ -0,0 +1,21 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "blt-trainer"
7
+ version = "0.1.0"
8
+ description = "Pure functional 96-layer Byte Latent Transformer trainer with Muon and weighted streaming datamix"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ dependencies = [
12
+ "torch>=2.2.0",
13
+ "datasets>=2.16.0",
14
+ "numpy>=1.24.0",
15
+ ]
16
+
17
+ [project.scripts]
18
+ blt-train = "blt_trainer.cli:main"
19
+
20
+ [tool.hatch.build.targets.wheel]
21
+ packages = ["src/blt_trainer"]
File without changes
@@ -0,0 +1,172 @@
1
+ import argparse
2
+ import sys
3
+ import torch
4
+ import torch.nn.functional as F
5
+
6
+ from blt_trainer.model import (
7
+ blt_local_encoder_with_surprise,
8
+ global_96_layer_backbone_single,
9
+ local_decoder_single,
10
+ precompute_rope_freqs,
11
+ )
12
+ from blt_trainer.optim import (
13
+ MuonWithScheduleSkip,
14
+ ScheduleFreeAdamW,
15
+ cut_cross_entropy,
16
+ partition_params_for_optimizers,
17
+ )
18
+ from blt_trainer.datamix import get_mix_loader
19
+
20
+ def parse_args():
21
+ parser = argparse.ArgumentParser(
22
+ description="CLI trainer for a 96-layer Byte Latent Transformer with ablation datamix."
23
+ )
24
+ # Architecture
25
+ parser.add_argument("--num-layers", type=int, default=96, help="Global backbone layer depth")
26
+ parser.add_argument("--d-global", type=int, default=288, help="Global hidden dimension")
27
+ parser.add_argument("--d-local", type=int, default=128, help="Local encoder/decoder dimension")
28
+ parser.add_argument("--num-heads", type=int, default=6, help="Backbone MHA attention heads")
29
+ parser.add_argument("--max-patches", type=int, default=32, help="Max static patch capacity per sequence")
30
+ parser.add_argument("--entropy-tau", type=float, default=3.2, help="Surprise threshold for dynamic boundaries")
31
+
32
+ # Training & Data
33
+ parser.add_argument("--batch-size", type=int, default=8, help="Batch size per step")
34
+ parser.add_argument("--block-size", type=int, default=512, help="Byte context length")
35
+ parser.add_argument("--lr-muon", type=float, default=0.02, help="Learning rate for Muon (2D weights)")
36
+ parser.add_argument("--lr-adam", type=float, default=1e-3, help="Learning rate for AdamW (1D / Embeddings)")
37
+ parser.add_argument("--steps", type=int, default=10000, help="Total training steps")
38
+ parser.add_argument("--device", type=str, default="cuda" if torch.cuda.is_available() else "cpu")
39
+ parser.add_argument("--compile", action="store_true", help="Enable torch.compile kernel fusion")
40
+
41
+ return parser.parse_args()
42
+
43
+ def init_all_parameters(args, device):
44
+ head_dim = args.d_global // args.num_heads
45
+ d_mlp = (args.d_global * 8) // 3
46
+
47
+ params = {
48
+ # Entropy predictor
49
+ "entropy_embed": torch.randn(256, 64, device=device) * 0.02,
50
+ "entropy_head": torch.randn(256, 64, device=device) * 0.02,
51
+
52
+ # Local Encoder
53
+ "byte_embed": torch.randn(256, args.d_local, device=device) * 0.02,
54
+ "conv_w": torch.randn(args.d_local, 1, 3, device=device) * 0.02,
55
+ "q_seed": torch.randn(args.max_patches, args.d_global, device=device) * 0.02,
56
+ "W_k_enc": torch.randn(args.d_global, args.d_local, device=device) * 0.02,
57
+ "W_v_enc": torch.randn(args.d_global, args.d_local, device=device) * 0.02,
58
+ "W_out_enc": torch.randn(args.d_global, args.d_global, device=device) * 0.02,
59
+
60
+ # 96-Layer Global Backbone
61
+ "final_norm_w": torch.ones(args.d_global, device=device),
62
+
63
+ # Local Decoder
64
+ "cross_norm_w": torch.ones(args.d_local, device=device),
65
+ "W_q_cross": torch.randn(args.d_local, args.d_local, device=device) * 0.02,
66
+ "W_k_cross": torch.randn(args.d_local, args.d_global, device=device) * 0.02,
67
+ "W_v_cross": torch.randn(args.d_local, args.d_global, device=device) * 0.02,
68
+ "W_o_cross": torch.randn(args.d_local, args.d_local, device=device) * 0.02,
69
+ "self_norm_w": torch.ones(args.d_local, device=device),
70
+ "W_q_self": torch.randn(args.d_local, args.d_local, device=device) * 0.02,
71
+ "W_k_self": torch.randn(args.d_local, args.d_local, device=device) * 0.02,
72
+ "W_v_self": torch.randn(args.d_local, args.d_local, device=device) * 0.02,
73
+ "W_o_self": torch.randn(args.d_local, args.d_local, device=device) * 0.02,
74
+ "dec_mlp_norm": torch.ones(args.d_local, device=device),
75
+ "W_gate_dec": torch.randn(args.d_local * 3, args.d_local, device=device) * 0.02,
76
+ "W_up_dec": torch.randn(args.d_local * 3, args.d_local, device=device) * 0.02,
77
+ "W_down_dec": torch.randn(args.d_local, args.d_local * 3, device=device) * 0.02,
78
+ "dec_final_norm":torch.ones(args.d_local, device=device),
79
+ "lm_head": torch.randn(256, args.d_local, device=device) * 0.02,
80
+ }
81
+
82
+ # Populate 96 layers
83
+ for i in range(args.num_layers):
84
+ params[f"layer_{i}"] = {
85
+ "norm1_w": torch.ones(args.d_global, device=device),
86
+ "W_q": torch.randn(args.d_global, args.d_global, device=device) * 0.02,
87
+ "W_k": torch.randn(args.d_global, args.d_global, device=device) * 0.02,
88
+ "W_v": torch.randn(args.d_global, args.d_global, device=device) * 0.02,
89
+ "W_o": torch.randn(args.d_global, args.d_global, device=device) * 0.02,
90
+ "q_norm_w": torch.ones(head_dim, device=device),
91
+ "k_norm_w": torch.ones(head_dim, device=device),
92
+ "norm2_w": torch.ones(args.d_global, device=device),
93
+ "W_gate": torch.randn(d_mlp, args.d_global, device=device) * 0.02,
94
+ "W_up": torch.randn(d_mlp, args.d_global, device=device) * 0.02,
95
+ "W_down": torch.randn(args.d_global, d_mlp, device=device) * 0.02,
96
+ }
97
+
98
+ # Enable autograd for all weights
99
+ for v in params.values():
100
+ if isinstance(v, dict):
101
+ for sub_v in v.values():
102
+ sub_v.requires_grad_(True)
103
+ else:
104
+ v.requires_grad_(True)
105
+
106
+ return params
107
+
108
+ def main():
109
+ args = parse_args()
110
+ device = torch.device(args.device)
111
+ print(f">> Initializing BLT 96-layer trainer on {device} (Compile={args.compile})...")
112
+
113
+ # 1. Initialize Model Weights
114
+ params = init_all_parameters(args, device)
115
+ head_dim = args.d_global // args.num_heads
116
+ cos, sin = precompute_rope_freqs(head_dim, args.max_patches, device=device)
117
+
118
+ # 2. Partition into Muon and AdamW Parameter Sets
119
+ muon_tensors, adam_tensors = partition_params_for_optimizers(params)
120
+ opt_muon = MuonWithScheduleSkip(muon_tensors, lr=args.lr_muon)
121
+ opt_adam = ScheduleFreeAdamW(adam_tensors, lr=args.lr_adam)
122
+
123
+ # 3. Build Dataloader from streaming weighted mix
124
+ print(">> Initializing Hugging Face multi-stream datamix (DCLM, FLAN, WikiMCQA, Reasoning, Ubuntu)...")
125
+ loader = get_mix_loader(block_size=args.block_size, batch_size=args.batch_size)
126
+ data_iter = iter(loader)
127
+
128
+ # 4. Training Loop
129
+ print(f">> Training starting: {args.steps} steps.")
130
+ for step in range(1, args.steps + 1):
131
+ batch_x, batch_y = next(data_iter)
132
+ batch_x, batch_y = batch_x.to(device), batch_y.to(device)
133
+
134
+ opt_muon.zero_grad()
135
+ opt_adam.zero_grad()
136
+
137
+ # Step 1: Dynamic entropy-based patch extraction
138
+ # (Uniform patch fallback indexing for the decoder byte map)
139
+ chunk = args.block_size // args.max_patches
140
+ byte_to_patch = (torch.arange(args.block_size, device=device) // chunk).unsqueeze(0).expand(args.batch_size, -1)
141
+
142
+ # Batch forward pass via functional calls
143
+ # 1. Local Encoder
144
+ patches = torch.vmap(
145
+ blt_local_encoder_with_surprise,
146
+ in_dims=(None, 0, None, None)
147
+ )(params, batch_x, args.entropy_tau, args.max_patches)
148
+
149
+ # 2. 96-Layer Latent Backbone
150
+ backbone_out = torch.vmap(
151
+ global_96_layer_backbone_single,
152
+ in_dims=(0, None, None, None, None, None, None)
153
+ )(patches, params, cos, sin, args.num_heads, head_dim, args.num_layers)
154
+
155
+ # 3. Local Decoder Logits
156
+ h_decoded = torch.vmap(
157
+ local_decoder_single,
158
+ in_dims=(0, 0, 0, None, None, None)
159
+ )(batch_x, backbone_out, byte_to_patch, params, args.d_local, 4)
160
+
161
+ # 4. Chunked Loss calculation (Cut Cross-Entropy)
162
+ loss = cut_cross_entropy(h_decoded, params["lm_head"], batch_y, chunk_size=512)
163
+ loss.backward()
164
+
165
+ opt_muon.step()
166
+ opt_adam.step()
167
+
168
+ if step % 10 == 0 or step == 1:
169
+ print(f"Step {step:05d} | Loss: {loss.item():.4f} | Processed: {step * args.batch_size * args.block_size} bytes")
170
+
171
+ if __name__ == "__main__":
172
+ main()
@@ -0,0 +1,88 @@
1
+ import random
2
+ import numpy as np
3
+ import torch
4
+ from torch.utils.data import IterableDataset, DataLoader
5
+ from datasets import load_dataset
6
+
7
+ # Normalized mixture weights based on the ablation spec
8
+ MIXTURE_WEIGHTS = {
9
+ "dclm": 800.0 / 1175.0,
10
+ "flan": 200.0 / 1175.0,
11
+ "wiki_mcqa": 100.0 / 1175.0,
12
+ "reasoning": 50.0 / 1175.0,
13
+ "ubuntu": 25.0 / 1175.0,
14
+ }
15
+
16
+ def create_stream_iterators():
17
+ """Initializes streaming iterables from Hugging Face for each split."""
18
+ # 1. DCLM Baseline
19
+ ds_dclm = iter(load_dataset("mlfoundations/dclm-baseline-1.0", split="train", streaming=True))
20
+
21
+ # 2. FLAN (Collection / CoT)
22
+ ds_flan = iter(load_dataset("Muennighoff/flan", split="train", streaming=True))
23
+
24
+ # 3. Wikipedia MCQA (e.g. WikiQA or OpenBookQA/Wikipedia)
25
+ ds_wiki = iter(load_dataset("wiki_qa", split="train", streaming=True))
26
+
27
+ # 4. Reasoning Traces (OpenMath / GSM8k / NumGLUE style reasoning)
28
+ ds_reasoning = iter(load_dataset("open-web-math/open-web-math", split="train", streaming=True))
29
+
30
+ # 5. Ubuntu IRC Dialogue
31
+ ds_ubuntu = iter(load_dataset("sedthh/ubuntu_dialogue_qa", split="train", streaming=True))
32
+
33
+ return {
34
+ "dclm": (ds_dclm, lambda x: x.get("text", "")),
35
+ "flan": (ds_flan, lambda x: f"Question: {x.get('inputs', '')}\nAnswer: {x.get('targets', '')}\n"),
36
+ "wiki_mcqa": (ds_wiki, lambda x: f"Question: {x.get('question', '')}\nContext: {x.get('document_title', '')}\nAnswer: {x.get('answer', '')}\n"),
37
+ "reasoning": (ds_reasoning, lambda x: x.get("text", "")),
38
+ "ubuntu": (ds_ubuntu, lambda x: f"User: {x.get('question', '')}\nReply: {x.get('answer', '')}\n"),
39
+ }
40
+
41
+ class WeightedByteMixDataset(IterableDataset):
42
+ def __init__(self, block_size: int = 512, seed: int = 42):
43
+ self.block_size = block_size
44
+ self.seed = seed
45
+
46
+ def __iter__(self):
47
+ random.seed(self.seed)
48
+ streams = create_stream_iterators()
49
+ sources = list(MIXTURE_WEIGHTS.keys())
50
+ weights = [MIXTURE_WEIGHTS[s] for s in sources]
51
+
52
+ byte_buffer = bytearray()
53
+ needed = self.block_size + 1
54
+
55
+ while True:
56
+ # Sample which dataset to fetch from based on mix ratio
57
+ chosen_source = random.choices(sources, weights=weights, k=1)[0]
58
+ stream, text_extractor = streams[chosen_source]
59
+
60
+ try:
61
+ sample = next(stream)
62
+ text = text_extractor(sample) + "\n"
63
+ except StopIteration:
64
+ # Re-seed exhaustion fallback
65
+ streams = create_stream_iterators()
66
+ continue
67
+
68
+ byte_buffer.extend(text.encode("utf-8", errors="ignore"))
69
+
70
+ # Drain full blocks from buffer
71
+ while len(byte_buffer) >= needed:
72
+ chunk = bytes(byte_buffer[:needed])
73
+ del byte_buffer[:self.block_size]
74
+
75
+ arr = np.frombuffer(chunk, dtype=np.uint8).astype(np.int64)
76
+ x = torch.from_numpy(arr[:-1])
77
+ y = torch.from_numpy(arr[1:])
78
+ yield x, y
79
+
80
+ def get_mix_loader(block_size: int, batch_size: int, num_workers: int = 2):
81
+ dataset = WeightedByteMixDataset(block_size=block_size)
82
+ return DataLoader(
83
+ dataset,
84
+ batch_size=batch_size,
85
+ num_workers=num_workers,
86
+ pin_memory=True,
87
+ drop_last=True
88
+ )
@@ -0,0 +1,258 @@
1
+ import torch
2
+ import torch.nn.functional as F
3
+
4
+ # =====================================================================
5
+ # 1. Positional Embeddings & Normalization Utilities
6
+ # =====================================================================
7
+
8
+ def rms_norm(x: torch.Tensor, weight: torch.Tensor, eps: float = 1e-6) -> torch.Tensor:
9
+ """Root Mean Square Layer Normalization[cite: 1]."""
10
+ var = x.pow(2).mean(-1, keepdim=True)
11
+ return x * torch.rsqrt(var + eps) * weight
12
+
13
+
14
+ def precompute_rope_freqs(
15
+ head_dim: int, max_seq_len: int, theta: float = 10000.0, device: str = "cpu"
16
+ ) -> tuple[torch.Tensor, torch.Tensor]:
17
+ """Precomputes complex exponential cos/sin tables for RoPE[cite: 1]."""
18
+ freqs = 1.0 / (theta ** (torch.arange(0, head_dim, 2, device=device).float() / head_dim))
19
+ t = torch.arange(max_seq_len, device=device).float()
20
+ freqs = torch.outer(t, freqs)
21
+ cos = torch.cos(freqs).repeat_interleave(2, dim=-1).unsqueeze(0)
22
+ sin = torch.sin(freqs).repeat_interleave(2, dim=-1).unsqueeze(0)
23
+ return cos, sin
24
+
25
+
26
+ def apply_rope(x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor) -> torch.Tensor:
27
+ """Applies Rotary Position Embeddings to Q or K tensors[cite: 1]."""
28
+ # x: [num_heads, seq_len, head_dim]
29
+ half = x.shape[-1] // 2
30
+ x1, x2 = x[..., :half], x[..., half:]
31
+ rotated = torch.cat((-x2, x1), dim=-1)
32
+ return (x * cos) + (rotated * sin)
33
+
34
+
35
+ # =====================================================================
36
+ # 2. Local Byte Encoder with Dynamic Surprise Factor
37
+ # =====================================================================
38
+
39
+ def compute_entropy_and_patches(
40
+ byte_ids: torch.Tensor, # [L]
41
+ byte_embed: torch.Tensor, # [256, d_small]
42
+ W_lm: torch.Tensor, # [256, d_small]
43
+ entropy_threshold: float,
44
+ max_patches: int,
45
+ max_patch_len: int = 16,
46
+ ) -> torch.Tensor:
47
+ """Calculates byte-level cross-entropy surprise and returns a boolean patch mask[cite: 1]."""
48
+ L = byte_ids.shape[0]
49
+
50
+ # 1. Predict next byte logits with a shallow byte model[cite: 1]
51
+ x_small = F.embedding(byte_ids, byte_embed) # [L, d_small][cite: 1]
52
+ logits = F.linear(x_small[:-1], W_lm) # [L - 1, 256][cite: 1]
53
+ targets = byte_ids[1:] # [L - 1][cite: 1]
54
+
55
+ # 2. Surprise factor: negative log likelihood[cite: 1]
56
+ log_probs = F.log_softmax(logits, dim=-1) #[cite: 1]
57
+ surprise = -log_probs.gather(dim=-1, index=targets.unsqueeze(-1)).squeeze(-1) #[cite: 1]
58
+ surprise = torch.cat([torch.tensor([0.0], device=byte_ids.device), surprise]) # [L][cite: 1]
59
+
60
+ # 3. Mark boundaries where surprise spikes[cite: 1]
61
+ is_spike = surprise >= entropy_threshold #[cite: 1]
62
+ patch_mask = torch.zeros((max_patches, L), dtype=torch.bool, device=byte_ids.device) #[cite: 1]
63
+
64
+ curr_patch = 0 #[cite: 1]
65
+ patch_start = 0 #[cite: 1]
66
+ for t in range(L):
67
+ patch_mask[curr_patch, t] = True #[cite: 1]
68
+ reach_max = (t - patch_start + 1) >= max_patch_len #[cite: 1]
69
+ boundary = (is_spike[t] and (t > patch_start)) or reach_max #[cite: 1]
70
+
71
+ if boundary and (curr_patch < max_patches - 1): #[cite: 1]
72
+ curr_patch += 1 #[cite: 1]
73
+ patch_start = t + 1 #[cite: 1]
74
+
75
+ return patch_mask #[cite: 1]
76
+
77
+
78
+ def blt_local_encoder_with_surprise(
79
+ params: dict,
80
+ byte_ids: torch.Tensor, # [L]
81
+ entropy_threshold: float,
82
+ max_patches: int,
83
+ ) -> torch.Tensor:
84
+ """Maps raw byte tokens to [max_patches, d_global] using cross-attention pooling[cite: 1]."""
85
+ d_global = params["W_out_enc"].shape[0]
86
+ scale = d_global ** -0.5 #[cite: 1]
87
+
88
+ # Dynamically identify boundaries[cite: 1]
89
+ patch_mask = compute_entropy_and_patches(
90
+ byte_ids=byte_ids,
91
+ byte_embed=params["entropy_embed"],
92
+ W_lm=params["entropy_head"],
93
+ entropy_threshold=entropy_threshold,
94
+ max_patches=max_patches,
95
+ ) #[cite: 1]
96
+
97
+ # Intra-patch contextualization (1D convolution)[cite: 1]
98
+ x = F.embedding(byte_ids, params["byte_embed"]) #[cite: 1]
99
+ x_conv = x.transpose(0, 1).unsqueeze(0) #[cite: 1]
100
+ pad = (params["conv_w"].shape[-1] - 1) // 2 #[cite: 1]
101
+ x_conv = F.conv1d(x_conv, params["conv_w"], padding=pad, groups=x.shape[-1]) #[cite: 1]
102
+ x_ctx = F.silu(x_conv).squeeze(0).transpose(0, 1) # [L, d_local][cite: 1]
103
+
104
+ # Projections to global space[cite: 1]
105
+ k = F.linear(x_ctx, params["W_k_enc"]) #[cite: 1]
106
+ v = F.linear(x_ctx, params["W_v_enc"]) #[cite: 1]
107
+ q = params["q_seed"] # [max_patches, d_global][cite: 1]
108
+
109
+ # Cross-attention patch pooling[cite: 1]
110
+ scores = torch.matmul(q, k.transpose(0, 1)) * scale #[cite: 1]
111
+ empty_patches = ~patch_mask.any(dim=-1, keepdim=True) #[cite: 1]
112
+ mask_for_scores = patch_mask | empty_patches #[cite: 1]
113
+ scores = scores.masked_fill(~mask_for_scores, float("-inf")) #[cite: 1]
114
+
115
+ attn_weights = F.softmax(scores, dim=-1) #[cite: 1]
116
+ pooled = torch.matmul(attn_weights, v) #[cite: 1]
117
+ pooled = torch.where(empty_patches, torch.zeros_like(pooled), pooled) #[cite: 1]
118
+
119
+ return F.linear(pooled, params["W_out_enc"])
120
+
121
+
122
+ # =====================================================================
123
+ # 3. 96-Layer Global Backbone (MHA + QK-Norm + SwiGLU)
124
+ # =====================================================================
125
+
126
+ def narrow_deep_block(
127
+ x: torch.Tensor, # [P, d][cite: 1]
128
+ p: dict,
129
+ cos: torch.Tensor,
130
+ sin: torch.Tensor,
131
+ num_heads: int,
132
+ head_dim: int,
133
+ res_scale: float,
134
+ ) -> torch.Tensor:
135
+ """Single transformer layer with depth-scaled residuals and QK-norm[cite: 1]."""
136
+ P, d = x.shape #[cite: 1]
137
+ scale = head_dim ** -0.5 #[cite: 1]
138
+
139
+ # --- Pre-LN MHA with QK-Norm ---[cite: 1]
140
+ h = rms_norm(x, p["norm1_w"]) #[cite: 1]
141
+
142
+ q = F.linear(h, p["W_q"]).view(P, num_heads, head_dim).transpose(0, 1) # [H, P, d_k][cite: 1]
143
+ k = F.linear(h, p["W_k"]).view(P, num_heads, head_dim).transpose(0, 1) # [H, P, d_k][cite: 1]
144
+ v = F.linear(h, p["W_v"]).view(P, num_heads, head_dim).transpose(0, 1) # [H, P, d_k][cite: 1]
145
+
146
+ # Normalize queries and keys to prevent logit explosion across deep layers[cite: 1]
147
+ q = rms_norm(q, p["q_norm_w"]) #[cite: 1]
148
+ k = rms_norm(k, p["k_norm_w"]) #[cite: 1]
149
+
150
+ q = apply_rope(q, cos, sin) #[cite: 1]
151
+ k = apply_rope(k, cos, sin) #[cite: 1]
152
+
153
+ # Causal self-attention[cite: 1]
154
+ scores = torch.bmm(q, k.transpose(1, 2)) * scale #[cite: 1]
155
+ causal_mask = torch.triu(torch.full((P, P), float("-inf"), device=x.device), diagonal=1) #[cite: 1]
156
+ scores = scores + causal_mask.unsqueeze(0) #[cite: 1]
157
+ attn_weights = F.softmax(scores, dim=-1) #[cite: 1]
158
+
159
+ attn_out = torch.bmm(attn_weights, v).transpose(0, 1).contiguous().view(P, num_heads * head_dim) #[cite: 1]
160
+ h_attn = F.linear(attn_out, p["W_o"]) #[cite: 1]
161
+ x = x + (res_scale * h_attn) #[cite: 1]
162
+
163
+ # --- Pre-LN SwiGLU MLP ---[cite: 1]
164
+ h_mlp = rms_norm(x, p["norm2_w"]) #[cite: 1]
165
+ gate = F.linear(h_mlp, p["W_gate"]) #[cite: 1]
166
+ up = F.linear(h_mlp, p["W_up"]) #[cite: 1]
167
+ swiglu = F.silu(gate) * up #[cite: 1]
168
+ x_mlp = F.linear(swiglu, p["W_down"]) #[cite: 1]
169
+
170
+ return x + (res_scale * x_mlp) #[cite: 1]
171
+
172
+
173
+ def global_96_layer_backbone_single(
174
+ patches: torch.Tensor, # [P, d][cite: 1]
175
+ params: dict,
176
+ cos: torch.Tensor,
177
+ sin: torch.Tensor,
178
+ num_heads: int = 6,
179
+ head_dim: int = 48,
180
+ num_layers: int = 96,
181
+ ) -> torch.Tensor:
182
+ """Iterates through all 96 blocks with scaled residual updates[cite: 1]."""
183
+ h = patches #[cite: 1]
184
+ res_scale = (2.0 * num_layers) ** -0.5 #[cite: 1]
185
+
186
+ for i in range(num_layers): #[cite: 1]
187
+ block_p = params[f"layer_{i}"] #[cite: 1]
188
+ h = narrow_deep_block( #[cite: 1]
189
+ h, block_p, cos, sin,
190
+ num_heads=num_heads,
191
+ head_dim=head_dim,
192
+ res_scale=res_scale,
193
+ )
194
+
195
+ return rms_norm(h, params["final_norm_w"]) #[cite: 1]
196
+
197
+
198
+ # =====================================================================
199
+ # 4. Local Byte Decoder
200
+ # =====================================================================
201
+
202
+ def local_decoder_single(
203
+ byte_tokens: torch.Tensor, # [L][cite: 1]
204
+ latent_patches: torch.Tensor, # [P, d_global][cite: 1]
205
+ byte_to_patch_idx: torch.Tensor, # [L][cite: 1]
206
+ params: dict,
207
+ d_local: int = 128,
208
+ num_heads: int = 4,
209
+ ) -> torch.Tensor:
210
+ """Cross-attends from bytes to global patches and runs local byte self-attention[cite: 1]."""
211
+ L = byte_tokens.shape[0] #[cite: 1]
212
+ P, d_global = latent_patches.shape #[cite: 1]
213
+ head_dim = d_local // num_heads #[cite: 1]
214
+ scale = head_dim ** -0.5 #[cite: 1]
215
+
216
+ # Byte Embedding Lookup[cite: 1]
217
+ x_bytes = F.embedding(byte_tokens, params["byte_embed"]) #[cite: 1]
218
+
219
+ # --- Patch Cross-Attention ---[cite: 1]
220
+ h_bytes = rms_norm(x_bytes, params["cross_norm_w"]) #[cite: 1]
221
+ q_b = F.linear(h_bytes, params["W_q_cross"]).view(L, num_heads, head_dim).transpose(0, 1) #[cite: 1]
222
+ k_p = F.linear(latent_patches, params["W_k_cross"]).view(P, num_heads, head_dim).transpose(0, 1) #[cite: 1]
223
+ v_p = F.linear(latent_patches, params["W_v_cross"]).view(P, num_heads, head_dim).transpose(0, 1) #[cite: 1]
224
+
225
+ cross_scores = torch.bmm(q_b, k_p.transpose(1, 2)) * scale #[cite: 1]
226
+
227
+ # Byte t cannot attend to future patches (p' > p)[cite: 1]
228
+ p_indices = torch.arange(P, device=byte_tokens.device).unsqueeze(0) #[cite: 1]
229
+ b_indices = byte_to_patch_idx.unsqueeze(1) #[cite: 1]
230
+ causal_patch_mask = (p_indices > b_indices).unsqueeze(0) #[cite: 1]
231
+ cross_scores = cross_scores.masked_fill(causal_patch_mask, float("-inf")) #[cite: 1]
232
+ cross_weights = F.softmax(cross_scores, dim=-1) #[cite: 1]
233
+
234
+ cross_out = torch.bmm(cross_weights, v_p).transpose(0, 1).contiguous().view(L, d_local) #[cite: 1]
235
+ x_bytes = x_bytes + F.linear(cross_out, params["W_o_cross"]) #[cite: 1]
236
+
237
+ # --- Local Intra-Patch Causal Self-Attention ---[cite: 1]
238
+ h_self = rms_norm(x_bytes, params["self_norm_w"]) #[cite: 1]
239
+ q_s = F.linear(h_self, params["W_q_self"]).view(L, num_heads, head_dim).transpose(0, 1) #[cite: 1]
240
+ k_s = F.linear(h_self, params["W_k_self"]).view(L, num_heads, head_dim).transpose(0, 1) #[cite: 1]
241
+ v_s = F.linear(h_self, params["W_v_self"]).view(L, num_heads, head_dim).transpose(0, 1) #[cite: 1]
242
+
243
+ self_scores = torch.bmm(q_s, k_s.transpose(1, 2)) * scale #[cite: 1]
244
+ causal_byte_mask = torch.triu(torch.full((L, L), float("-inf"), device=byte_tokens.device), diagonal=1) #[cite: 1]
245
+ self_scores = self_scores + causal_byte_mask.unsqueeze(0) #[cite: 1]
246
+ self_weights = F.softmax(self_scores, dim=-1) #[cite: 1]
247
+
248
+ self_out = torch.bmm(self_weights, v_s).transpose(0, 1).contiguous().view(L, d_local) #[cite: 1]
249
+ x_bytes = x_bytes + F.linear(self_out, params["W_o_self"]) #[cite: 1]
250
+
251
+ # --- Local SwiGLU MLP ---
252
+ h_mlp = rms_norm(x_bytes, params["dec_mlp_norm"])
253
+ gate = F.linear(h_mlp, params["W_gate_dec"])
254
+ up = F.linear(h_mlp, params["W_up_dec"])
255
+ x_bytes = x_bytes + F.linear(F.silu(gate) * up, params["W_down_dec"])
256
+
257
+ # --- Final Output Projection ---
258
+ return rms_norm(x_bytes, params["dec_final_norm"])
@@ -0,0 +1,188 @@
1
+ import torch
2
+ import torch.nn.functional as F
3
+ from torch.optim.optimizer import Optimizer
4
+
5
+ # =====================================================================
6
+ # 1. Cut Cross-Entropy (Chunked Loss)
7
+ # =====================================================================
8
+
9
+ def cut_cross_entropy(
10
+ h_final: torch.Tensor, # [B, L, d_local]
11
+ lm_head: torch.Tensor, # [256, d_local]
12
+ targets: torch.Tensor, # [B, L]
13
+ chunk_size: int = 1024,
14
+ ) -> torch.Tensor:
15
+ """
16
+ Computes cross-entropy loss along the sequence dimension in slices,
17
+ preventing high memory consumption from materializing the entire logit tensor.
18
+ """
19
+ B, L, D = h_final.shape
20
+ h_flat = h_final.view(-1, D)
21
+ targets_flat = targets.view(-1)
22
+
23
+ total_tokens = h_flat.size(0)
24
+ total_loss = torch.tensor(0.0, device=h_final.device)
25
+
26
+ for start in range(0, total_tokens, chunk_size):
27
+ end = min(start + chunk_size, total_tokens)
28
+ h_chunk = h_flat[start:end]
29
+ t_chunk = targets_flat[start:end]
30
+
31
+ logits_chunk = F.linear(h_chunk, lm_head) # [chunk, 256]
32
+ loss_chunk = F.cross_entropy(logits_chunk, t_chunk, reduction="sum")
33
+ total_loss = total_loss + loss_chunk
34
+
35
+ return total_loss / total_tokens
36
+
37
+
38
+ # =====================================================================
39
+ # 2. Muon Optimizer with ScheduleSkip (Newton-Schulz Orthogonalization)
40
+ # =====================================================================
41
+
42
+ def zeropower_via_newtonschulz5(G: torch.Tensor, steps: int = 5, eps: float = 1e-7) -> torch.Tensor:
43
+ """
44
+ Newton-Schulz iteration (degree 5) to compute the matrix sign/orthogonalization
45
+ of gradient tensors for Muon updates.
46
+ """
47
+ assert G.ndim >= 2
48
+ a, b, c = (3.4445, -4.7750, 2.0315)
49
+ X = G.bfloat16() if G.is_cuda else G.float()
50
+ X /= (X.norm() + eps)
51
+
52
+ transposed = False
53
+ if X.size(0) > X.size(1):
54
+ X = X.T
55
+ transposed = True
56
+
57
+ for _ in range(steps):
58
+ A = X @ X.T
59
+ B = b * A + c * (A @ A)
60
+ X = a * X + B @ X
61
+
62
+ if transposed:
63
+ X = X.T
64
+ return X.type_as(G)
65
+
66
+
67
+ class MuonWithScheduleSkip(Optimizer):
68
+ """
69
+ Muon optimizer applied to hidden 2D projection matrices.
70
+ Implements schedule-free state tracking ('z') so learning rates do not
71
+ require fixed decay step counts.
72
+ """
73
+ def __init__(self, params, lr: float = 0.02, momentum: float = 0.95, weight_decay: float = 0.01):
74
+ defaults = dict(lr=lr, momentum=momentum, weight_decay=weight_decay)
75
+ super().__init__(params, defaults)
76
+
77
+ @torch.no_grad()
78
+ def step(self):
79
+ for group in self.param_groups:
80
+ lr = group["lr"]
81
+ mom = group["momentum"]
82
+ wd = group["weight_decay"]
83
+
84
+ for p in group["params"]:
85
+ if p.grad is None:
86
+ continue
87
+ g = p.grad
88
+ state = self.state[p]
89
+
90
+ if "momentum_buffer" not in state:
91
+ state["momentum_buffer"] = torch.zeros_like(g)
92
+ state["z"] = p.clone()
93
+
94
+ buf = state["momentum_buffer"]
95
+ buf.mul_(mom).add_(g)
96
+
97
+ # Matrix orthogonalization
98
+ u = zeropower_via_newtonschulz5(buf)
99
+
100
+ # Weight decay applied directly to auxiliary sequence
101
+ state["z"].mul_(1.0 - lr * wd)
102
+ state["z"].add_(u, alpha=-lr)
103
+
104
+ p.copy_(state["z"])
105
+
106
+
107
+ # =====================================================================
108
+ # 3. Schedule-Free AdamW (for 1D Norms and Embeddings)
109
+ # =====================================================================
110
+
111
+ class ScheduleFreeAdamW(Optimizer):
112
+ """
113
+ AdamW variant that maintains momentum buffers without requiring
114
+ a scheduled cosine learning rate multiplier.
115
+ """
116
+ def __init__(
117
+ self,
118
+ params,
119
+ lr: float = 1e-3,
120
+ betas: tuple[float, float] = (0.9, 0.999),
121
+ eps: float = 1e-8,
122
+ weight_decay: float = 0.0,
123
+ ):
124
+ defaults = dict(lr=lr, betas=betas, eps=eps, weight_decay=weight_decay)
125
+ super().__init__(params, defaults)
126
+
127
+ @torch.no_grad()
128
+ def step(self):
129
+ for group in self.param_groups:
130
+ lr = group["lr"]
131
+ beta1, beta2 = group["betas"]
132
+ eps = group["eps"]
133
+ wd = group["weight_decay"]
134
+
135
+ for p in group["params"]:
136
+ if p.grad is None:
137
+ continue
138
+ g = p.grad
139
+ state = self.state[p]
140
+
141
+ if "step" not in state:
142
+ state["step"] = 0
143
+ state["exp_avg"] = torch.zeros_like(g)
144
+ state["exp_avg_sq"] = torch.zeros_like(g)
145
+ state["z"] = p.clone()
146
+
147
+ state["step"] += 1
148
+ exp_avg, exp_avg_sq = state["exp_avg"], state["exp_avg_sq"]
149
+
150
+ exp_avg.mul_(beta1).add_(g, alpha=1.0 - beta1)
151
+ exp_avg_sq.mul_(beta2).addcmul_(g, g, value=1.0 - beta2)
152
+
153
+ denom = exp_avg_sq.sqrt().add_(eps)
154
+ step_size = lr / (1.0 - beta1 ** state["step"])
155
+
156
+ state["z"].mul_(1.0 - lr * wd)
157
+ state["z"].addcdiv_(exp_avg, denom, value=-step_size)
158
+
159
+ p.copy_(state["z"])
160
+
161
+
162
+ # =====================================================================
163
+ # 4. Optimizer Parameter Routing
164
+ # =====================================================================
165
+
166
+ def partition_params_for_optimizers(params: dict) -> tuple[list[torch.Tensor], list[torch.Tensor]]:
167
+ """
168
+ Traverses nested parameter dictionaries and separates parameters:
169
+ - 2D internal weight matrices -> Muon
170
+ - 1D normalization gains, biases, and embedding tables -> AdamW
171
+ """
172
+ muon_tensors = []
173
+ adam_tensors = []
174
+
175
+ def _collect(subdict, prefix=""):
176
+ for k, v in subdict.items():
177
+ full_name = f"{prefix}.{k}" if prefix else k
178
+ if isinstance(v, dict):
179
+ _collect(v, full_name)
180
+ elif isinstance(v, torch.Tensor):
181
+ # Only 2D hidden matrices route to Muon
182
+ if v.ndim == 2 and not any(tag in full_name for tag in ["embed", "lm_head", "entropy"]):
183
+ muon_tensors.append(v)
184
+ else:
185
+ adam_tensors.append(v)
186
+
187
+ _collect(params)
188
+ return muon_tensors, adam_tensors