# /// script # requires-python = ">=3.13" # dependencies = [ # "datasets==5.0.0", # "huggingface-hub==1.24.0", # "tokenizers==0.23.1", # "triton==3.7.1", # ] # /// import marimo __generated_with = "0.23.15" app = marimo.App( width="medium", layout_file="layouts/notebook.grid.json", auto_download=["html"], ) @app.cell def _(): #!/usr/bin/env python3 """One-file training pipeline for the faithful ~157M CubicV7-Hier LM. Stages ------ 0. Train a 32K byte-level BPE and prepare deterministic local caches. 1. 3B-token multilingual/code/math base pretraining. 2. Dialogue/instruction SFT (SmolTalk + Aya EN/RU). 3. Verified reasoning SFT (OpenR1-Math-220k, shortest correct trace). 4. Offline human-preference alignment with DPO (Anthropic HH-RLHF + corrected UltraFeedback). 5. Evaluate, generate samples, save safetensors and upload a complete folder to asilarkness/llm-150m. This file deliberately contains the model, faithful Cubic depth memory, the hierarchical late retrieval path, Muon+AdamW, data preparation, training, resume, inference and Hub publishing. No hidden local modules are required. Useful environment overrides: CUBIC_MODE=train|chat (default: train) CUBIC_ROOT=/marimo/cubic_hier_150m_gemma4 CUBIC_PRETRAIN_TOKENS=3000000000 CUBIC_SEQ_LEN=4096 CUBIC_MICRO_BATCH=16 CUBIC_ACCUM=1 CUBIC_COMPILE=1 CUBIC_COMPILE_MODE=default CUBIC_CHECKPOINT_EVERY=0 CUBIC_HF_CHECKPOINT_UPLOAD=1 CUBIC_HF_CHECKPOINT_REPO=asilarkness/next CUBIC_HF_CHECKPOINT_MINUTES=60 CUBIC_REASONING=0|1 (chat mode; direct or mode) CUBIC_HF_REPO=asilarkness/llm-150m CUBIC_UPLOAD=1 HF_TOKEN=... (optional after `hf auth login`) The default run is intentionally a real training run, not a smoke test. Set CUBIC_PRETRAIN_TOKENS=2000000 and the dataset limits below for a short end-to-end test before committing GPU-days. """ from __future__ import annotations import copy import gc import hashlib import importlib.util import json import math import os import random import re import shutil import subprocess import sys import threading import time from array import array from collections import defaultdict from contextlib import nullcontext from dataclasses import asdict, dataclass from pathlib import Path from typing import Any, Iterator, Sequence def ensure_dependencies() -> None: packages = { "torch": "torch", "datasets": "datasets>=3.2.0", "tokenizers": "tokenizers>=0.20.0", "huggingface_hub": "huggingface_hub[hf_xet]>=0.27.0", "safetensors": "safetensors>=0.4.5", "liger_kernel": "liger-kernel>=0.5.2", } missing = [spec for module, spec in packages.items() if importlib.util.find_spec(module) is None] if missing: print("Installing dependencies:", ", ".join(missing), flush=True) subprocess.check_call([sys.executable, "-m", "pip", "install", "-q", *missing]) os.environ.setdefault("TOKENIZERS_PARALLELISM", "true") os.environ.setdefault("RAYON_NUM_THREADS", os.environ.get("CUBIC_TOKENIZER_THREADS", "4")) os.environ.setdefault("HF_XET_HIGH_PERFORMANCE", "1") os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True") ensure_dependencies() import torch import torch.nn as nn import torch.nn.functional as F from torch.utils.checkpoint import checkpoint # ------------------------------------------------------------------------------------------------- # Configuration # ------------------------------------------------------------------------------------------------- def env_int(name: str, default: int) -> int: return int(os.environ.get(name, str(default)).replace("_", "")) def env_float(name: str, default: float) -> float: return float(os.environ.get(name, str(default))) @dataclass(frozen=True) class Config: vocab_size: int = 32_768 dim: int = 800 depth: int = 16 n_heads: int = 10 mlp_hidden: int = 2_176 depth_rank: int = 200 depth_heads: int = 5 seq_len: int = env_int("CUBIC_SEQ_LEN", 4_096) max_context: int = env_int("CUBIC_MAX_CONTEXT", 8_192) group_chunk: int = env_int("CUBIC_GROUP_CHUNK", 128) group_keep: int = env_int("CUBIC_GROUP_KEEP", 4) initial_mix: float = 0.18 recency_bias: float = 0.30 rope_base: float = 500_000.0 norm_eps: float = 1e-5 @property def head_dim(self) -> int: return self.dim // self.n_heads @property def depth_head_dim(self) -> int: return self.depth_rank // self.depth_heads CFG = Config() EXPECTED_PARAMETER_COUNT = 157_065_144 if CFG.dim % CFG.n_heads or CFG.depth_rank % CFG.depth_heads: raise ValueError("dim/head counts are not divisible") if CFG.seq_len > CFG.max_context: raise ValueError("CUBIC_SEQ_LEN must not exceed CUBIC_MAX_CONTEXT") SEED = env_int("CUBIC_SEED", 20_260_722) MODE = os.environ.get("CUBIC_MODE", "train").lower() ARCH_VERSION = "cubic-hier-v7-gemma4-150m-v2" # A new default root deliberately prevents an older v1 cache/checkpoint from # being silently reused with the changed tokenizer mixture and architecture. ROOT = Path(os.environ.get("CUBIC_ROOT", "/marimo/cubic_hier_150m_gemma4")) WORK = ROOT / "work" CACHE = ROOT / "cache" CHECKPOINTS = ROOT / "checkpoints" EXPORT = ROOT / "export" TOKENIZER_PATH = WORK / "tokenizer.json" LATEST = CHECKPOINTS / "latest.pt" BEST = CHECKPOINTS / "best.pt" FINAL = CHECKPOINTS / "final.pt" DPO_REFERENCE = CHECKPOINTS / "dpo_reference.pt" REPORT = CHECKPOINTS / "samples.jsonl" HF_REPO = os.environ.get("CUBIC_HF_REPO", "asilarkness/llm-150m") PRETRAIN_TOKENS = env_int("CUBIC_PRETRAIN_TOKENS", 3_000_000_000) PRETRAIN_VAL_TOKENS = env_int("CUBIC_PRETRAIN_VAL_TOKENS", 8_000_000) TOKENIZER_DOCS = env_int("CUBIC_TOKENIZER_DOCS", 200_000) SMOLTALK_LIMIT = env_int("CUBIC_SMOLTALK_LIMIT", 500_000) AYA_LIMIT = env_int("CUBIC_AYA_LIMIT", 200_000) REASON_LIMIT = env_int("CUBIC_REASON_LIMIT", 100_000) HH_LIMIT = env_int("CUBIC_HH_LIMIT", 150_000) UF_LIMIT = env_int("CUBIC_UF_LIMIT", 100_000) # RTX PRO 6000 96 GB: batch 16 with no activation recomputation is faster per # token and uses less peak memory than batch 32 with half the blocks recomputed. MICRO_BATCH = env_int("CUBIC_MICRO_BATCH", 16) GRAD_ACCUM = env_int("CUBIC_ACCUM", 1) # Same effective DPO batch (8) and exactly the same eight examples per update, # but two launches instead of eight. DPO_MICRO_BATCH = env_int("CUBIC_DPO_MICRO_BATCH", 4) DPO_ACCUM = env_int("CUBIC_DPO_ACCUM", 2) SFT_EPOCHS = env_float("CUBIC_SFT_EPOCHS", 1.0) REASON_EPOCHS = env_float("CUBIC_REASON_EPOCHS", 1.5) DPO_EPOCHS = env_float("CUBIC_DPO_EPOCHS", 1.0) DPO_BETA = env_float("CUBIC_DPO_BETA", 0.10) DPO_SFT_WEIGHT = env_float("CUBIC_DPO_SFT_WEIGHT", 0.05) MTP_PRETRAIN_WEIGHT = env_float("CUBIC_MTP_PRETRAIN_WEIGHT", 0.30) MTP_DECAY_WEIGHT = env_float("CUBIC_MTP_DECAY_WEIGHT", 0.10) MTP_SFT_WEIGHT = env_float("CUBIC_MTP_SFT_WEIGHT", 0.10) COMPILE = os.environ.get("CUBIC_COMPILE", "1") == "1" COMPILE_MODE = os.environ.get("CUBIC_COMPILE_MODE", "default") # Checkpoint every second block. This preserves forward/backward mathematics # while spending the otherwise idle 96 GB VRAM to avoid half of recomputation. # Use 1 for the old low-memory behavior, or 0 to disable recomputation entirely. CHECKPOINT_EVERY = env_int("CUBIC_CHECKPOINT_EVERY", 0) if CHECKPOINT_EVERY < 0: raise ValueError("CUBIC_CHECKPOINT_EVERY must be >= 0") UPLOAD = os.environ.get("CUBIC_UPLOAD", "1") == "1" CHECKPOINT_MINUTES = env_float("CUBIC_CHECKPOINT_MINUTES", 30.0) HF_CHECKPOINT_UPLOAD = os.environ.get("CUBIC_HF_CHECKPOINT_UPLOAD", "1") == "1" HF_CHECKPOINT_REPO = os.environ.get("CUBIC_HF_CHECKPOINT_REPO", "asilarkness/next") HF_CHECKPOINT_MINUTES = env_float("CUBIC_HF_CHECKPOINT_MINUTES", 60.0) HF_RESUME_DOWNLOAD = os.environ.get("CUBIC_HF_RESUME_DOWNLOAD", "1") == "1" ALLOW_COLD_START = os.environ.get("CUBIC_ALLOW_COLD_START", "0") == "1" LOG_EVERY = env_int("CUBIC_LOG_EVERY", 10) EVAL_EVERY = env_int("CUBIC_EVAL_EVERY", 500) EVAL_BATCHES = env_int("CUBIC_EVAL_BATCHES", 16) EVAL_BATCH_SIZE = env_int("CUBIC_EVAL_BATCH_SIZE", 8) GRAD_CLIP = 1.0 PRETRAIN_TOKENS_FILE = CACHE / "pretrain_train_u16.bin" PRETRAIN_VAL_FILE = CACHE / "pretrain_val_u16.bin" SFT_TOKENS_FILE = CACHE / "sft_tokens_u16.bin" SFT_MASK_FILE = CACHE / "sft_mask_u8.bin" REASON_TOKENS_FILE = CACHE / "reason_tokens_u16.bin" REASON_MASK_FILE = CACHE / "reason_mask_u8.bin" PREF_FILES = { "chosen_tokens": CACHE / "pref_chosen_tokens_u16.bin", "chosen_mask": CACHE / "pref_chosen_mask_u8.bin", "rejected_tokens": CACHE / "pref_rejected_tokens_u16.bin", "rejected_mask": CACHE / "pref_rejected_mask_u8.bin", } META_FILE = CACHE / "metadata.json" SPECIAL_TOKENS = ( "", "", "", "", "<|system|>", "<|user|>", "<|assistant|>", "", "", ) def setup_runtime() -> str: for path in (ROOT, WORK, CACHE, CHECKPOINTS, EXPORT): path.mkdir(parents=True, exist_ok=True) random.seed(SEED) torch.manual_seed(SEED) if not torch.cuda.is_available(): raise RuntimeError("This full run requires a CUDA GPU") torch.cuda.manual_seed_all(SEED) torch.backends.cuda.matmul.allow_tf32 = True torch.backends.cudnn.allow_tf32 = True torch.set_float32_matmul_precision("high") return "cuda" DEVICE = "cuda" if torch.cuda.is_available() else "cpu" AMP_DTYPE = torch.bfloat16 if torch.cuda.is_available() and torch.cuda.is_bf16_supported() else torch.float16 def amp_context(): return torch.autocast("cuda", dtype=AMP_DTYPE) if DEVICE == "cuda" else nullcontext() def stable_hash(value: str) -> int: return int.from_bytes(hashlib.blake2b(value.encode("utf-8", "ignore"), digest_size=8).digest(), "little") def atomic_json(data: Any, path: Path) -> None: temporary = path.with_suffix(path.suffix + ".tmp") temporary.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8") os.replace(temporary, path) # ------------------------------------------------------------------------------------------------- # Dataset streaming, tokenizer and compact caches # ------------------------------------------------------------------------------------------------- def stream_pretrain(seed: int = SEED) -> Iterator[str]: """Deterministic quality mixture: web EN/RU, deduplicated code and math. The 72/13/10/5 mix keeps the bilingual identity of the original run but fixes its largest pretraining hole: virtually no code or mathematical language before reasoning SFT. All four inputs are streamed, so the one-file pipeline still needs no manual corpus download. """ from datasets import load_dataset sources = { "web_en": iter(load_dataset( "HuggingFaceFW/fineweb-edu", name="sample-10BT", split="train", streaming=True, ).shuffle(seed=seed, buffer_size=10_000)), "web_ru": iter(load_dataset( "HuggingFaceFW/fineweb-2", name="rus_Cyrl", split="train", streaming=True, ).shuffle(seed=seed + 1, buffer_size=10_000)), "code": iter(load_dataset( "codeparrot/codeparrot-clean", split="train", streaming=True, ).shuffle(seed=seed + 2, buffer_size=10_000)), "math": iter(load_dataset( "HuggingFaceTB/finemath", name="finemath-4plus", split="train", streaming=True, ).shuffle(seed=seed + 3, buffer_size=10_000)), } probabilities = (("web_en", 0.72), ("web_ru", 0.13), ("code", 0.10), ("math", 0.05)) rng = random.Random(seed) while True: draw = rng.random() cumulative = 0.0 source_name = probabilities[-1][0] for name, probability in probabilities: cumulative += probability if draw < cumulative: source_name = name break try: record = next(sources[source_name]) except StopIteration: # Every selected source is much larger than this 3B-token run. If # a Hub revision ever changes that, fail loudly instead of silently # changing the declared mixture. raise RuntimeError(f"pretraining source ended early: {source_name}") text = record.get("content", "") if source_name == "code" else record.get("text", "") if isinstance(text, str) and len(text) >= 200: yield text def train_tokenizer(): from tokenizers import Tokenizer, decoders, models, normalizers, pre_tokenizers, trainers if TOKENIZER_PATH.exists(): print(f"Tokenizer already present: {TOKENIZER_PATH}") return Tokenizer.from_file(str(TOKENIZER_PATH)) print(f"Training byte-level BPE: vocab={CFG.vocab_size:,}, documents={TOKENIZER_DOCS:,}") tokenizer = Tokenizer(models.BPE(unk_token="")) tokenizer.normalizer = normalizers.NFC() # Gemma-style digit splitting improves arithmetic compositionality, while # ByteLevel preserves whitespace/code formatting and guarantees byte-level # coverage through its complete initial alphabet. tokenizer.pre_tokenizer = pre_tokenizers.Sequence([ pre_tokenizers.Digits(individual_digits=True), pre_tokenizers.ByteLevel(add_prefix_space=False), ]) tokenizer.decoder = decoders.ByteLevel() trainer = trainers.BpeTrainer( vocab_size=CFG.vocab_size, min_frequency=2, special_tokens=list(SPECIAL_TOKENS), initial_alphabet=pre_tokenizers.ByteLevel.alphabet(), show_progress=True, ) def texts() -> Iterator[str]: for index, text in enumerate(stream_pretrain(SEED + 10)): if index >= TOKENIZER_DOCS: break yield text tokenizer.train_from_iterator(texts(), trainer=trainer, length=TOKENIZER_DOCS) if tokenizer.get_vocab_size() != CFG.vocab_size: raise RuntimeError(f"tokenizer produced {tokenizer.get_vocab_size()}, expected {CFG.vocab_size}") tokenizer.save(str(TOKENIZER_PATH)) return tokenizer class FlatU16Writer: def __init__(self, path: Path): self.path = path # An interrupted multi-hour corpus build resumes by appending. It may # repeat a small prefix of documents, but never discards gigabytes of # already-tokenized data and never publishes a half-written final file. self.count = path.stat().st_size // 2 if path.exists() else 0 self.file = path.open("ab") self.buffer = array("H") def add(self, ids: Sequence[int], limit: int | None = None) -> int: if limit is not None: ids = ids[: max(0, limit - self.count)] self.buffer.extend(ids) self.count += len(ids) if len(self.buffer) >= 2_000_000: self.buffer.tofile(self.file) self.buffer = array("H") return len(ids) def flush(self, durable: bool = False) -> None: if self.buffer: self.buffer.tofile(self.file) self.buffer = array("H") self.file.flush() if durable: os.fsync(self.file.fileno()) def close(self) -> None: self.flush() self.file.close() def prepare_pretrain_cache(tokenizer) -> dict[str, int]: wanted_train_bytes = PRETRAIN_TOKENS * 2 if ( PRETRAIN_TOKENS_FILE.exists() and PRETRAIN_VAL_FILE.exists() and PRETRAIN_TOKENS_FILE.stat().st_size >= wanted_train_bytes and PRETRAIN_VAL_FILE.stat().st_size >= PRETRAIN_VAL_TOKENS * 2 ): return { "pretrain_tokens": PRETRAIN_TOKENS_FILE.stat().st_size // 2, "pretrain_val_tokens": PRETRAIN_VAL_FILE.stat().st_size // 2, } print(f"Encoding {PRETRAIN_TOKENS / 1e9:.2f}B pretraining tokens ...") train_tmp = PRETRAIN_TOKENS_FILE.with_suffix(".tmp") val_tmp = PRETRAIN_VAL_FILE.with_suffix(".tmp") progress_path = CACHE / "pretrain_encoding_progress.json" encode_batch_size = env_int("CUBIC_ENCODING_BATCH", 256) train = FlatU16Writer(train_tmp) val_ready = ( PRETRAIN_VAL_FILE.exists() and PRETRAIN_VAL_FILE.stat().st_size >= PRETRAIN_VAL_TOKENS * 2 ) val = None if val_ready else FlatU16Writer(val_tmp) val_count = PRETRAIN_VAL_TOKENS if val_ready else val.count resume_documents = 0 if progress_path.exists(): try: progress = json.loads(progress_path.read_text(encoding="utf-8")) saved_train = int(progress.get("train_tokens", -1)) saved_val = int(progress.get("val_tokens", -1)) # A hard SIGKILL may leave a few post-cursor bytes on disk. Roll # those uncommitted bytes back to the last atomic cursor instead # of abandoning the cursor and restarting the entire stream. if 0 <= saved_train <= train.count and 0 <= saved_val <= val_count: if saved_train != train.count: train.file.truncate(saved_train * 2) train.count = saved_train if val is not None and saved_val != val.count: val.file.truncate(saved_val * 2) val.count = saved_val val_count = saved_val resume_documents = int(progress.get("documents", 0)) except (OSError, ValueError, TypeError, json.JSONDecodeError): resume_documents = 0 eos = tokenizer.token_to_id("") started = time.perf_counter() initial_total = train.count + val_count print( f" resume train={train.count / 1e9:.3f}B val={val_count / 1e6:.1f}M " f"documents={resume_documents:,} batch={encode_batch_size} " f"threads={os.environ['RAYON_NUM_THREADS']}", flush=True, ) def save_progress(documents: int) -> None: # Cursor is recorded only after all corresponding token bytes are # durable, so a hard process/container restart cannot skip data. train.flush(durable=True) if val is not None: val.flush(durable=True) atomic_json({ "documents": documents, "train_tokens": train.count, "val_tokens": val_count if val is None else val.count, "updated_at": time.time(), }, progress_path) try: stream = enumerate(stream_pretrain(SEED + 20)) pending_texts: list[str] = [] pending_last_document = resume_documents completed_documents = resume_documents for document_index, text in stream: if document_index < resume_documents: continue pending_texts.append(text) pending_last_document = document_index + 1 if len(pending_texts) < encode_batch_size: continue encodings = tokenizer.encode_batch(pending_texts) for source_text, encoding in zip(pending_texts, encodings): ids = encoding.ids + [eos] # Once validation is complete, every document goes directly # into train; this avoids rebuilding/duplicating val on resume. use_val = ( val is not None and val.count < PRETRAIN_VAL_TOKENS and stable_hash(source_text[:512]) % 101 == 0 ) if use_val: val.add(ids, PRETRAIN_VAL_TOKENS) else: train.add(ids, PRETRAIN_TOKENS) pending_texts.clear() completed_documents = pending_last_document if pending_last_document % (encode_batch_size * 20) == 0: save_progress(completed_documents) current_val = val_count if val is None else val.count new_tokens = max(0, train.count + current_val - initial_total) rate = new_tokens / max(1e-6, time.perf_counter() - started) print( f" documents={pending_last_document:,} " f"train={train.count / 1e9:.3f}B val={current_val / 1e6:.1f}M " f"{rate:,.0f} tok/s", flush=True, ) current_val = val_count if val is None else val.count if train.count >= PRETRAIN_TOKENS and current_val >= PRETRAIN_VAL_TOKENS: break finally: # KeyboardInterrupt, notebook stop and ordinary exceptions all leave a # durable cursor. At worst a partly processed batch is duplicated; # already encoded gigabytes are never discarded or skipped. save_progress(completed_documents) train.close() if val is not None: val.close() final_val_count = val_count if val is None else val.count if train.count < PRETRAIN_TOKENS or final_val_count < PRETRAIN_VAL_TOKENS: raise RuntimeError("pretraining stream ended before cache targets were reached") os.replace(train_tmp, PRETRAIN_TOKENS_FILE) if not val_ready: os.replace(val_tmp, PRETRAIN_VAL_FILE) elif val_tmp.exists(): # Stale partial validation data is no longer needed after the complete # final validation file has been verified. val_tmp.unlink() progress_path.unlink(missing_ok=True) gc.collect() if torch.cuda.is_available(): torch.cuda.empty_cache() print("Pretraining cache finalized; CPU encoding memory released. Starting GPU training.", flush=True) return {"pretrain_tokens": train.count, "pretrain_val_tokens": final_val_count} class PackedRecords: """Packs variable-length supervised samples into fixed seq_len+1 records.""" def __init__(self, token_path: Path, mask_path: Path, pad_id: int): self.token_tmp = token_path.with_suffix(".tmp") self.mask_tmp = mask_path.with_suffix(".tmp") self.token_path, self.mask_path = token_path, mask_path self.token_file = self.token_tmp.open("wb") self.mask_file = self.mask_tmp.open("wb") self.ids: list[int] = [] self.mask: list[int] = [] self.pad_id = pad_id self.record_len = CFG.seq_len + 1 self.records = 0 def _flush(self) -> None: if not self.ids: return padding = self.record_len - len(self.ids) array("H", self.ids + [self.pad_id] * padding).tofile(self.token_file) array("B", self.mask + [0] * padding).tofile(self.mask_file) self.ids.clear() self.mask.clear() self.records += 1 def add(self, ids: Sequence[int], mask: Sequence[int]) -> None: if len(ids) != len(mask): raise ValueError("token/mask length mismatch") # Preserve the final answer if an example exceeds the context. if len(ids) > self.record_len: supervised = next((i for i, value in enumerate(mask) if value), len(ids) - 1) start = max(0, min(supervised - self.record_len // 3, len(ids) - self.record_len)) ids, mask = ids[start:start + self.record_len], mask[start:start + self.record_len] if len(self.ids) + len(ids) > self.record_len: self._flush() self.ids.extend(ids) self.mask.extend(mask) def close(self) -> int: self._flush() self.token_file.close() self.mask_file.close() os.replace(self.token_tmp, self.token_path) os.replace(self.mask_tmp, self.mask_path) return self.records def role_id(tokenizer, role: str) -> int: role = "assistant" if role in ("assistant", "gpt", "model") else "user" if role in ("user", "human") else "system" return tokenizer.token_to_id(f"<|{role}|>") def normalize_messages(value: Any) -> list[dict[str, str]]: if not isinstance(value, list): return [] result = [] for item in value: if not isinstance(item, dict): continue role = str(item.get("role", item.get("from", ""))).lower() content = item.get("content", item.get("value", "")) if role and isinstance(content, str) and content.strip(): result.append({"role": role, "content": content.strip()}) return result DIRECT_SYSTEM = ( "Answer directly, accurately and concisely. Do not invent facts. " "If the request is ambiguous, state the assumption you use." ) REASONING_SYSTEM = ( "Solve the task carefully. Put the internal solution trace between " " and , then give a clear final answer." ) def with_system_mode(messages: Sequence[dict[str, str]], reasoning: bool) -> list[dict[str, str]]: result = list(messages) if result and result[0]["role"] == "system": return result instruction = REASONING_SYSTEM if reasoning else DIRECT_SYSTEM return [{"role": "system", "content": instruction}, *result] def encode_chat(tokenizer, messages: Sequence[dict[str, str]], supervise_assistant: bool = True) -> tuple[list[int], list[int]]: bos, eos = tokenizer.token_to_id(""), tokenizer.token_to_id("") ids, mask = [bos], [0] for message in messages: role = str(message["role"]).lower() content = str(message["content"]) segment = [role_id(tokenizer, role), *tokenizer.encode("\n" + content + "\n").ids, eos] supervised = supervise_assistant and role in ("assistant", "gpt", "model") ids.extend(segment) mask.extend([int(supervised)] * len(segment)) return ids, mask def cached_record_count(token_path: Path) -> int: return token_path.stat().st_size // (2 * (CFG.seq_len + 1)) def prepare_dialogue_cache(tokenizer) -> int: if SFT_TOKENS_FILE.exists() and SFT_MASK_FILE.exists(): return cached_record_count(SFT_TOKENS_FILE) from datasets import load_dataset print("Preparing dialogue SFT cache (SmolTalk + Aya EN/RU) ...") writer = PackedRecords(SFT_TOKENS_FILE, SFT_MASK_FILE, tokenizer.token_to_id("")) accepted = 0 seen: set[int] = set() smol = load_dataset("HuggingFaceTB/smol-smoltalk", split="train", streaming=True) for record in smol: messages = normalize_messages(record.get("messages", record.get("conversations"))) fingerprint = stable_hash(json.dumps(messages, ensure_ascii=False, sort_keys=True)) if ( messages and fingerprint not in seen and any(m["role"] in ("assistant", "gpt", "model") for m in messages) ): seen.add(fingerprint) writer.add(*encode_chat(tokenizer, with_system_mode(messages, reasoning=False))) accepted += 1 if accepted >= SMOLTALK_LIMIT: break aya_count = 0 aya = load_dataset("CohereLabs/aya_dataset", split="train", streaming=True) for record in aya: language = str(record.get("language", "")).lower() if language not in ("english", "russian", "eng", "rus", "en", "ru"): continue prompt, answer = record.get("inputs"), record.get("targets") if isinstance(prompt, str) and isinstance(answer, str) and prompt.strip() and answer.strip(): fingerprint = stable_hash(prompt.strip() + "\0" + answer.strip()) if fingerprint in seen: continue seen.add(fingerprint) writer.add(*encode_chat(tokenizer, with_system_mode([ {"role": "user", "content": prompt}, {"role": "assistant", "content": answer}, ], reasoning=False))) aya_count += 1 if aya_count >= AYA_LIMIT: break records = writer.close() print(f" dialogue examples={accepted + aya_count:,}, packed records={records:,}") return records def shortest_correct_reasoning(record: dict[str, Any]) -> str: generations = record.get("generations") or [] if isinstance(generations, str): generations = [generations] math_flags = record.get("correctness_math_verify") or [] judge_flags = record.get("correctness_llama") or [] complete_flags = record.get("is_reasoning_complete") or [] candidates = [] for index, generation in enumerate(generations): if not isinstance(generation, str) or not generation.strip(): continue verified = ( (index < len(math_flags) and bool(math_flags[index])) or (index < len(judge_flags) and bool(judge_flags[index])) ) complete = not complete_flags or index >= len(complete_flags) or bool(complete_flags[index]) if verified and complete: candidates.append(generation.strip()) if candidates: return min(candidates, key=len) solution = record.get("solution", "") return solution.strip() if isinstance(solution, str) else "" def prepare_reasoning_cache(tokenizer) -> int: if REASON_TOKENS_FILE.exists() and REASON_MASK_FILE.exists(): return cached_record_count(REASON_TOKENS_FILE) from datasets import load_dataset print("Preparing verified reasoning SFT cache (OpenR1-Math-220k) ...") writer = PackedRecords(REASON_TOKENS_FILE, REASON_MASK_FILE, tokenizer.token_to_id("")) accepted = 0 seen: set[int] = set() dataset = load_dataset("open-r1/OpenR1-Math-220k", name="default", split="train", streaming=True) for record in dataset: problem = record.get("problem", "") answer = shortest_correct_reasoning(record) if not isinstance(problem, str) or not problem.strip() or not answer: continue fingerprint = stable_hash(problem.strip() + "\0" + answer) if fingerprint in seen: continue seen.add(fingerprint) if "" not in answer: answer = f"\n{answer}\n" writer.add(*encode_chat(tokenizer, with_system_mode([ {"role": "user", "content": problem.strip()}, {"role": "assistant", "content": answer}, ], reasoning=True))) accepted += 1 if accepted >= REASON_LIMIT: break records = writer.close() print(f" reasoning examples={accepted:,}, packed records={records:,}") return records def parse_hh_transcript(text: str) -> list[dict[str, str]]: """Converts Anthropic's '\n\nHuman:' transcript format to messages.""" if not isinstance(text, str): return [] pieces = re.split(r"\n\n(Human|Assistant):\s*", text) messages: list[dict[str, str]] = [] # pieces: optional prefix, role, content, role, content, ... for index in range(1, len(pieces) - 1, 2): role = "user" if pieces[index] == "Human" else "assistant" content = pieces[index + 1].strip() if content: messages.append({"role": role, "content": content}) return messages def preference_pair_from_messages( chosen: Sequence[dict[str, str]], rejected: Sequence[dict[str, str]], ) -> tuple[list[dict[str, str]], str, str] | None: if not chosen or not rejected: return None if chosen[-1]["role"] != "assistant" or rejected[-1]["role"] != "assistant": return None prompt_chosen, prompt_rejected = list(chosen[:-1]), list(rejected[:-1]) # The prompt should be identical. A conservative common-prefix fallback # protects against harmless metadata differences without inventing context. common: list[dict[str, str]] = [] for left, right in zip(prompt_chosen, prompt_rejected): if left != right: break common.append(left) if not common: return None return common, chosen[-1]["content"], rejected[-1]["content"] def encode_preference_side(tokenizer, prompt: Sequence[dict[str, str]], response: str) -> tuple[list[int], list[int]]: prompt_ids, _ = encode_chat( tokenizer, with_system_mode(prompt, reasoning=False), supervise_assistant=False, ) eos = tokenizer.token_to_id("") response_ids = [ tokenizer.token_to_id("<|assistant|>"), *tokenizer.encode("\n" + response.strip() + "\n").ids, eos, ] ids = prompt_ids + response_ids mask = [0] * len(prompt_ids) + [1] * len(response_ids) limit = CFG.seq_len + 1 if len(ids) > limit: # Keep the entire preference signal whenever possible and trim the # oldest prompt turns first. first_response = len(prompt_ids) start = max(0, min(first_response - limit // 4, len(ids) - limit)) ids, mask = ids[start:start + limit], mask[start:start + limit] return ids, mask class PreferenceWriter: def __init__(self, tokenizer): self.tokenizer = tokenizer self.record_len = CFG.seq_len + 1 self.pad = tokenizer.token_to_id("") self.temporary = {name: path.with_suffix(".tmp") for name, path in PREF_FILES.items()} self.handles = {name: path.open("wb") for name, path in self.temporary.items()} self.records = 0 def _fixed(self, values: Sequence[int], pad: int) -> list[int]: return list(values[:self.record_len]) + [pad] * max(0, self.record_len - len(values)) def add(self, prompt: Sequence[dict[str, str]], chosen: str, rejected: str) -> None: chosen_ids, chosen_mask = encode_preference_side(self.tokenizer, prompt, chosen) rejected_ids, rejected_mask = encode_preference_side(self.tokenizer, prompt, rejected) if not any(chosen_mask) or not any(rejected_mask): return array("H", self._fixed(chosen_ids, self.pad)).tofile(self.handles["chosen_tokens"]) array("B", self._fixed(chosen_mask, 0)).tofile(self.handles["chosen_mask"]) array("H", self._fixed(rejected_ids, self.pad)).tofile(self.handles["rejected_tokens"]) array("B", self._fixed(rejected_mask, 0)).tofile(self.handles["rejected_mask"]) self.records += 1 def close(self) -> int: for handle in self.handles.values(): handle.close() for name, final in PREF_FILES.items(): os.replace(self.temporary[name], final) return self.records def prepare_preference_cache(tokenizer) -> int: if all(path.exists() for path in PREF_FILES.values()): return cached_record_count(PREF_FILES["chosen_tokens"]) from datasets import load_dataset print("Preparing DPO pairs (Anthropic HH-RLHF + corrected UltraFeedback) ...") writer = PreferenceWriter(tokenizer) seen: set[int] = set() hh_count = 0 for record in load_dataset("Anthropic/hh-rlhf", split="train", streaming=True): pair = preference_pair_from_messages( parse_hh_transcript(record.get("chosen", "")), parse_hh_transcript(record.get("rejected", "")), ) if pair: fingerprint = stable_hash(json.dumps(pair, ensure_ascii=False, sort_keys=True)) if fingerprint in seen: continue seen.add(fingerprint) writer.add(*pair) hh_count += 1 if hh_count >= HH_LIMIT: break uf_count = 0 for record in load_dataset("HuggingFaceH4/ultrafeedback_binarized", split="train_prefs", streaming=True): pair = preference_pair_from_messages( normalize_messages(record.get("chosen")), normalize_messages(record.get("rejected")), ) if pair: fingerprint = stable_hash(json.dumps(pair, ensure_ascii=False, sort_keys=True)) if fingerprint in seen: continue seen.add(fingerprint) writer.add(*pair) uf_count += 1 if uf_count >= UF_LIMIT: break records = writer.close() print(f" HH pairs={hh_count:,}, UltraFeedback pairs={uf_count:,}, total={records:,}") return records def prepare_everything(tokenizer) -> dict[str, int]: meta = prepare_pretrain_cache(tokenizer) meta["sft_records"] = prepare_dialogue_cache(tokenizer) meta["reason_records"] = prepare_reasoning_cache(tokenizer) meta["preference_records"] = prepare_preference_cache(tokenizer) meta["record_len"] = CFG.seq_len + 1 atomic_json(meta, META_FILE) return meta # ------------------------------------------------------------------------------------------------- # Faithful CubicV7 + hierarchical late retrieval # ------------------------------------------------------------------------------------------------- class RMSNorm(nn.Module): def __init__(self, dim: int, eps: float = 1e-5): super().__init__() self.weight = nn.Parameter(torch.ones(dim)) self.eps = eps def forward(self, x: torch.Tensor) -> torch.Tensor: normalized = x.float() * torch.rsqrt(x.float().pow(2).mean(-1, keepdim=True) + self.eps) # Keep activations in bf16/fp16 under autocast; a float32 norm weight # must not accidentally promote the full sequence tensor to float32. return normalized.to(x.dtype) * self.weight.to(x.dtype) class RotaryEmbedding(nn.Module): def __init__(self, head_dim: int, max_context: int, base: float): super().__init__() inverse = 1.0 / (base ** (torch.arange(0, head_dim, 2, dtype=torch.float32) / head_dim)) positions = torch.arange(max_context, dtype=torch.float32) frequencies = torch.outer(positions, inverse) self.register_buffer("cos", frequencies.cos(), persistent=False) self.register_buffer("sin", frequencies.sin(), persistent=False) def forward(self, length: int) -> tuple[torch.Tensor, torch.Tensor]: return self.cos[:length], self.sin[:length] def apply_rope(x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor) -> torch.Tensor: # x: [batch, heads, sequence, head_dim] x_even, x_odd = x[..., 0::2], x[..., 1::2] cos = cos[:x.shape[-2]].to(device=x.device, dtype=x.dtype).view(1, 1, x.shape[-2], -1) sin = sin[:x.shape[-2]].to(device=x.device, dtype=x.dtype).view(1, 1, x.shape[-2], -1) return torch.stack((x_even * cos - x_odd * sin, x_even * sin + x_odd * cos), dim=-1).flatten(-2) class SwiGLU(nn.Module): def __init__(self, config: Config): super().__init__() self.gate = nn.Linear(config.dim, config.mlp_hidden, bias=False) self.value = nn.Linear(config.dim, config.mlp_hidden, bias=False) self.down = nn.Linear(config.mlp_hidden, config.dim, bias=False) def forward(self, x: torch.Tensor) -> torch.Tensor: return self.down(F.silu(self.gate(x)) * self.value(x)) class DepthCompressor(nn.Module): """Shared compressor. The returned history is deliberately NOT detached.""" def __init__(self, config: Config): super().__init__() self.config = config self.norm = RMSNorm(config.dim, config.norm_eps) self.kv = nn.Linear(config.dim, 2 * config.depth_rank, bias=False) def forward(self, x: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]: batch, sequence, _ = x.shape kv = self.kv(self.norm(x)).view( batch, sequence, 2, self.config.depth_heads, self.config.depth_head_dim, ) return kv[:, :, 0], kv[:, :, 1] class CubicHierAttention(nn.Module): """Faithful V7 sequence attention and separate gated depth residual. Two penultimate layers use the validated cubic_hier approximation: cosine score against key-group means, causal top-group routing under no_grad, then one fused SDPA over the selected token groups. The last layer is full global causal attention so it can integrate all evidence selected below. Only the hard routing decision is non-differentiable; surviving Q/K/V scores keep their gradients. """ def __init__(self, config: Config, layer_index: int): super().__init__() self.config = config self.layer_index = layer_index # Gemma 4's useful invariant transferred without changing Cubic's # depth path: retrieval happens before, never instead of, the final # global integration layer. self.hierarchical = layer_index in (config.depth - 3, config.depth - 2) self.qkv = nn.Linear(config.dim, 3 * config.dim, bias=False) self.proj = nn.Linear(config.dim, config.dim, bias=False) # Per-head QK RMSNorm prevents attention-logit growth. It is applied # before RoPE and is independent of the faithful depth-memory channel. self.q_norm = RMSNorm(config.head_dim, config.norm_eps) self.k_norm = RMSNorm(config.head_dim, config.norm_eps) if self.hierarchical: self.log_temperature = nn.Parameter(torch.full((config.n_heads,), math.log(10.0))) causal_future = torch.ones(config.max_context, config.max_context, dtype=torch.bool).triu(1) self.register_buffer("causal_future", causal_future, persistent=False) n_groups = math.ceil(config.max_context / config.group_chunk) group_ids = torch.arange(n_groups) query_groups = torch.arange(config.max_context) // config.group_chunk self.register_buffer( "group_previous", query_groups.unsqueeze(1) > group_ids.unsqueeze(0), persistent=False, ) self.register_buffer( "group_current", query_groups.unsqueeze(1) == group_ids.unsqueeze(0), persistent=False, ) self.has_depth = layer_index > 0 self.has_depth_choice = layer_index > 1 if self.has_depth: self.depth_up = nn.Linear(config.depth_rank, config.dim, bias=False) if self.has_depth_choice: self.q_depth = nn.Linear(config.dim, config.depth_rank, bias=False) self.layer_bias = nn.Parameter( torch.linspace(-config.recency_bias, config.recency_bias, layer_index), ) self.depth_mix_logit = nn.Parameter(torch.full((config.dim,), math.atanh(config.initial_mix))) self.depth_content_gate = nn.Linear(config.dim, 1) self.depth_norm = RMSNorm(config.dim, config.norm_eps) self.agreement_scale = nn.Parameter(torch.zeros(1)) self.agreement_bias = nn.Parameter(torch.zeros(1)) nn.init.zeros_(self.depth_content_gate.weight) nn.init.zeros_(self.depth_content_gate.bias) def sequence_attention(self, x: torch.Tensor, rope: RotaryEmbedding) -> torch.Tensor: config = self.config batch, sequence, _ = x.shape q, k, v = self.qkv(x).split(config.dim, dim=-1) q = q.view(batch, sequence, config.n_heads, config.head_dim).transpose(1, 2) k = k.view(batch, sequence, config.n_heads, config.head_dim).transpose(1, 2) v = v.view(batch, sequence, config.n_heads, config.head_dim).transpose(1, 2) q, k = self.q_norm(q), self.k_norm(k) cos, sin = rope(sequence) q, k = apply_rope(q, cos, sin), apply_rope(k, cos, sin) if not self.hierarchical: out = F.scaled_dot_product_attention(q, k, v, is_causal=True, dropout_p=0.0) else: q = F.normalize(q, dim=-1) k = F.normalize(k, dim=-1) temperature = self.log_temperature.clamp(math.log(2.0), math.log(32.0)).exp() q = q * temperature.view(1, config.n_heads, 1, 1) future = self.causal_future[:sequence, :sequence].view(1, 1, sequence, sequence) with torch.no_grad(): padding = (-sequence) % config.group_chunk grouped_k = F.pad(k, (0, 0, 0, padding)) if padding else k n_groups = grouped_k.shape[-2] // config.group_chunk grouped_k = grouped_k.view( batch, config.n_heads, n_groups, config.group_chunk, config.head_dim, ).mean(dim=3) coarse = torch.matmul(q, grouped_k.transpose(-2, -1)) # Causal routing: summaries are used only for fully completed # groups. The current group is forced into the mask but never # scored through a mean containing future keys. previous = self.group_previous[:sequence, :n_groups].view(1, 1, sequence, n_groups) current = self.group_current[:sequence, :n_groups].view(1, 1, sequence, n_groups) coarse = coarse.masked_fill(~previous, float("-inf")) if config.group_keep > 1: keep_previous = min(config.group_keep - 1, n_groups) threshold = coarse.topk(keep_previous, dim=-1).values[..., -1:] group_keep = ((coarse >= threshold) & previous) | current else: group_keep = current token_keep = group_keep.repeat_interleave(config.group_chunk, dim=-1)[..., :sequence] keep_mask = token_keep & (~future) out = F.scaled_dot_product_attention( q, k, v, attn_mask=keep_mask, scale=1.0, dropout_p=0.0, ) return out.transpose(1, 2).contiguous().view(batch, sequence, config.dim) def depth_residual( self, x: torch.Tensor, history_k: Sequence[torch.Tensor], history_v: Sequence[torch.Tensor], ) -> torch.Tensor: config = self.config batch, sequence, _ = x.shape if self.has_depth_choice: layers = len(history_k) q = self.q_depth(x).view(batch * sequence, config.depth_heads, 1, config.depth_head_dim) k = torch.stack(tuple(history_k), dim=2).permute(0, 1, 3, 2, 4).reshape( batch * sequence, config.depth_heads, layers, config.depth_head_dim, ) v = torch.stack(tuple(history_v), dim=2).permute(0, 1, 3, 2, 4).reshape( batch * sequence, config.depth_heads, layers, config.depth_head_dim, ) # The depth axis is tiny (at most 15 states), so a manual softmax # is both cheap and more robust than dispatching SDPA here. On # PyTorch 2.11/CUDA 13, compiled efficient-attention backward can # produce a 65536-vs-65535 shape error for batch=16, seq=4096 when # its broadcast bias has zero strides. Sequence attention still # uses fused FlashAttention; only this small cross-layer selector # avoids the faulty backend. The formula is exactly standard # scaled dot-product attention plus the learned recency bias. scale = config.depth_head_dim ** -0.5 scores = torch.matmul(q, k.transpose(-2, -1)) * scale scores = scores + self.layer_bias.view(1, 1, 1, layers).to(scores.dtype) weights = torch.softmax(scores.float(), dim=-1).to(v.dtype) depth = torch.matmul(weights, v) depth = depth.reshape(batch, sequence, config.depth_rank) else: depth = history_v[0].reshape(batch, sequence, config.depth_rank) depth = self.depth_up(depth) agreement = ( F.normalize(x, dim=-1) * F.normalize(self.depth_norm(depth), dim=-1) ).sum(dim=-1, keepdim=True) agreement_gate = 2.0 * torch.sigmoid(self.agreement_scale * agreement + self.agreement_bias) content_gate = 2.0 * torch.sigmoid(self.depth_content_gate(x)) return agreement_gate * content_gate * self.depth_mix_logit.tanh() * depth def forward( self, x: torch.Tensor, rope: RotaryEmbedding, history_k: Sequence[torch.Tensor], history_v: Sequence[torch.Tensor], ) -> torch.Tensor: # V7 invariant: only sequence attention passes through `proj`. out = self.proj(self.sequence_attention(x, rope)) if self.has_depth: out = out + self.depth_residual(x, history_k, history_v) return out class CubicBlock(nn.Module): def __init__(self, config: Config, layer_index: int): super().__init__() self.norm1 = RMSNorm(config.dim, config.norm_eps) self.attn = CubicHierAttention(config, layer_index) self.norm2 = RMSNorm(config.dim, config.norm_eps) self.mlp = SwiGLU(config) self.ls1 = nn.Parameter(torch.ones(config.dim)) self.ls2 = nn.Parameter(torch.ones(config.dim)) def forward(self, x, rope, history_k, history_v): x = x + self.ls1 * self.attn(self.norm1(x), rope, history_k, history_v) return x + self.ls2 * self.mlp(self.norm2(x)) class CubicHierLM(nn.Module): def __init__(self, config: Config = CFG): super().__init__() self.config = config self.embed = nn.Embedding(config.vocab_size, config.dim) self.rope = RotaryEmbedding(config.head_dim, config.max_context, config.rope_base) self.blocks = nn.ModuleList([CubicBlock(config, index) for index in range(config.depth)]) self.depth_memory = DepthCompressor(config) self.final_norm = RMSNorm(config.dim, config.norm_eps) # One-token-ahead multi-token prediction head. Given h_t and the known # token x_(t+1), it predicts x_(t+2) through the tied LM embedding. # It is training-only: inference latency and checkpoint compatibility # remain straightforward, while each sequence supplies an extra dense # learning signal. self.mtp_hidden = nn.Linear(config.dim, config.dim, bias=False) self.mtp_token = nn.Linear(config.dim, config.dim, bias=False) self.mtp_norm = RMSNorm(config.dim, config.norm_eps) self.gradient_checkpointing = CHECKPOINT_EVERY > 0 self.checkpoint_every = CHECKPOINT_EVERY self.apply(self._init_weights) residual_std = 0.02 / math.sqrt(2.0 * config.depth) for block in self.blocks: nn.init.normal_(block.attn.proj.weight, mean=0.0, std=residual_std) nn.init.normal_(block.mlp.down.weight, mean=0.0, std=residual_std) if block.attn.has_depth: # Restore the faithful V7 zero-initialized content selector; # the global initializer above must not randomize this gate. nn.init.zeros_(block.attn.depth_content_gate.weight) nn.init.zeros_(block.attn.depth_content_gate.bias) def _init_weights(self, module: nn.Module) -> None: if isinstance(module, (nn.Linear, nn.Embedding)): nn.init.normal_(module.weight, mean=0.0, std=0.02) def forward(self, tokens: torch.Tensor) -> torch.Tensor: if tokens.shape[1] > self.config.max_context: raise ValueError(f"sequence {tokens.shape[1]} exceeds max_context={self.config.max_context}") x = self.embed(tokens) history_k: list[torch.Tensor] = [] history_v: list[torch.Tensor] = [] for index, block in enumerate(self.blocks): if index < self.config.depth - 1: new_k, new_v = self.depth_memory(x) # no detach: faithful V7 should_checkpoint = ( self.gradient_checkpointing and self.training and self.checkpoint_every > 0 and index % self.checkpoint_every == 0 ) if should_checkpoint: # Non-reentrant checkpointing records tensors captured by this # closure, including the entire differentiable depth history. x = checkpoint( lambda value, b=block, hk=tuple(history_k), hv=tuple(history_v): b(value, self.rope, hk, hv), x, use_reentrant=False, ) else: x = block(x, self.rope, history_k, history_v) if index < self.config.depth - 1: history_k.append(new_k) history_v.append(new_v) return self.final_norm(x) def logits(self, hidden: torch.Tensor) -> torch.Tensor: return F.linear(hidden, self.embed.weight) def mtp_conditioned_hidden(self, hidden: torch.Tensor, next_tokens: torch.Tensor) -> torch.Tensor: return self.mtp_norm(self.mtp_hidden(hidden) + self.mtp_token(self.embed(next_tokens))) # ------------------------------------------------------------------------------------------------- # Faithful hybrid Muon + AdamW # ------------------------------------------------------------------------------------------------- MUON_LR = 0.020 DEPTH_MUON_LR = 0.030 MUON_MOMENTUM = 0.95 MUON_NS_STEPS = env_int("CUBIC_MUON_NS", 4) MUON_WEIGHT_DECAY = 0.01 MUON_BUCKET_CHUNK = 8 AUX_LR = 3e-4 GATE_LR = 5e-3 AUX_WEIGHT_DECAY = 0.01 MIN_MUON_NUMEL = 4_096 @torch.no_grad() def zeropower_batched(updates: torch.Tensor, steps: int = MUON_NS_STEPS, eps: float = 1e-7) -> torch.Tensor: a, b, c = 3.4445, -4.7750, 2.0315 x = updates.to(torch.bfloat16) x = x / (x.norm(dim=(-2, -1), keepdim=True) + eps) for _ in range(steps): gram = x @ x.mT x = a * x + (b * gram + c * (gram @ gram)) @ x return x class CubicMuon(torch.optim.Optimizer): def __init__(self, muon_groups, adam_groups, lr_multiplier: float): super().__init__(muon_groups, dict( lr=MUON_LR, momentum=MUON_MOMENTUM, ns_steps=MUON_NS_STEPS, weight_decay=MUON_WEIGHT_DECAY, )) self.lr_multiplier = lr_multiplier self.muon_base_lrs = [float(group["lr"]) * lr_multiplier for group in self.param_groups] self.muon_depth = [bool(group.get("is_depth", False)) for group in self.param_groups] self.layouts: list[list[list[tuple[torch.Tensor, bool, float]]]] = [] for group in self.param_groups: buckets: dict[tuple[int, int], list[tuple[torch.Tensor, bool, float]]] = defaultdict(list) for parameter in group["params"]: rows, columns = parameter.shape transpose = rows > columns canonical = (min(rows, columns), max(rows, columns)) scale = math.sqrt(max(1.0, rows / columns)) buckets[canonical].append((parameter, transpose, scale)) self.layouts.append(list(buckets.values())) for group in adam_groups: group["lr"] *= lr_multiplier try: self.adamw = torch.optim.AdamW(adam_groups, betas=(0.9, 0.95), eps=1e-8, fused=True) except (TypeError, RuntimeError): self.adamw = torch.optim.AdamW(adam_groups, betas=(0.9, 0.95), eps=1e-8) self.adam_base_lrs = [float(group["lr"]) for group in self.adamw.param_groups] self.adam_gate = [bool(group.get("is_gate", False)) for group in self.adamw.param_groups] def set_lr_scale(self, scale: float, gate_scale: float, depth_boost: float) -> None: for group, base_lr, is_depth in zip(self.param_groups, self.muon_base_lrs, self.muon_depth): group["lr"] = base_lr * scale * (depth_boost if is_depth else 1.0) for group, base_lr, is_gate in zip(self.adamw.param_groups, self.adam_base_lrs, self.adam_gate): group["lr"] = base_lr * (gate_scale if is_gate else scale) @torch.no_grad() def step(self, closure=None): for group_index, group in enumerate(self.param_groups): lr, beta = group["lr"], group["momentum"] active = [parameter for parameter in group["params"] if parameter.grad is not None] if group["weight_decay"] and active: torch._foreach_mul_(active, 1.0 - lr * group["weight_decay"]) for bucket in self.layouts[group_index]: for start in range(0, len(bucket), MUON_BUCKET_CHUNK): prepared, metadata = [], [] for parameter, transpose, scale in bucket[start:start + MUON_BUCKET_CHUNK]: if parameter.grad is None: continue state = self.state[parameter] if "momentum_buffer" not in state: state["momentum_buffer"] = torch.zeros_like(parameter) momentum = state["momentum_buffer"] momentum.lerp_(parameter.grad, 1.0 - beta) # Nesterov update used by the validated V7 optimizer. update = torch.lerp(parameter.grad, momentum, beta) prepared.append(update.mT if transpose else update) metadata.append((parameter, transpose, scale)) if prepared: orthogonal = zeropower_batched(torch.stack(prepared), group["ns_steps"]) for update, (parameter, transpose, scale) in zip(orthogonal.unbind(0), metadata): parameter.add_(update.mT if transpose else update, alpha=-lr * scale) self.adamw.step() def zero_grad(self, set_to_none: bool = True) -> None: super().zero_grad(set_to_none=set_to_none) self.adamw.zero_grad(set_to_none=set_to_none) def build_optimizer(model: nn.Module, lr_multiplier: float) -> CubicMuon: muon, depth, auxiliary_decay, auxiliary_no_decay, gates = [], [], [], [], [] gate_names = ( # This matches the validated V7 split exactly. Agreement scalars and # cosine temperatures remain conservative AdamW auxiliaries (3e-4), # not fast 5e-3 gates. "depth_mix_logit", "depth_content_gate", "layer_bias", "ls1", "ls2", ) depth_names = ("q_depth", "depth_up", "depth_memory") forbidden_muon = ("embed", "norm") for name, parameter in model.named_parameters(): is_gate = any(fragment in name for fragment in gate_names) use_muon = ( parameter.ndim == 2 and parameter.numel() >= MIN_MUON_NUMEL and not any(fragment in name for fragment in forbidden_muon) and not is_gate ) if use_muon: (depth if any(fragment in name for fragment in depth_names) else muon).append(parameter) elif is_gate: gates.append(parameter) elif parameter.ndim < 2 or any(fragment in name for fragment in ("embed", "norm")): # Embeddings, norm scales and scalar controls are more stable # without AdamW decay in small language models. auxiliary_no_decay.append(parameter) else: auxiliary_decay.append(parameter) print( f"optimizer: Muon={len(muon)} + depth={len(depth)}, " f"AdamW decay={len(auxiliary_decay)} no_decay={len(auxiliary_no_decay)} " f"+ gates={len(gates)}, phase_lr={lr_multiplier:.3f}x", ) muon_groups = [ dict(params=muon, lr=MUON_LR, momentum=MUON_MOMENTUM, ns_steps=MUON_NS_STEPS, weight_decay=MUON_WEIGHT_DECAY, is_depth=False), dict(params=depth, lr=DEPTH_MUON_LR, momentum=MUON_MOMENTUM, ns_steps=MUON_NS_STEPS, weight_decay=MUON_WEIGHT_DECAY, is_depth=True), ] adam_groups = [ dict(params=auxiliary_decay, lr=AUX_LR, weight_decay=AUX_WEIGHT_DECAY, is_gate=False), dict(params=auxiliary_no_decay, lr=AUX_LR, weight_decay=0.0, is_gate=False), dict(params=gates, lr=GATE_LR, weight_decay=0.0, is_gate=True), ] return CubicMuon( [group for group in muon_groups if group["params"]], [group for group in adam_groups if group["params"]], lr_multiplier, ) def cosine_schedule(step: int, total_steps: int, warmup: int, floor: float = 0.10) -> float: if step < warmup: return (step + 1) / max(1, warmup) progress = min(1.0, max(0.0, (step - warmup) / max(1, total_steps - warmup - 1))) return floor + (1.0 - floor) * 0.5 * (1.0 + math.cos(math.pi * progress)) def wsd_schedule( step: int, total_steps: int, warmup: int, decay_fraction: float = 0.10, floor: float = 0.10, ) -> float: """Warmup-stable-decay schedule for long base pretraining. Unlike a full-run cosine, it does not starve the middle of a short 3B-token run of learning rate. The last 10% remains a clean annealing stage. """ if step < warmup: return (step + 1) / max(1, warmup) decay_steps = max(1, int(total_steps * decay_fraction)) decay_start = max(warmup, total_steps - decay_steps) if step < decay_start: return 1.0 progress = min(1.0, max(0.0, (step - decay_start) / max(1, total_steps - decay_start - 1))) return floor + (1.0 - floor) * 0.5 * (1.0 + math.cos(math.pi * progress)) def gate_schedule(step: int, base_scale: float) -> float: opened = min(1.0, (step + 1) / 200.0) return opened * (0.25 + 0.75 * base_scale) def mtp_loss_weight(phase_name: str, step: int, total_steps: int) -> float: if phase_name != "pretrain": return MTP_SFT_WEIGHT decay_start = max(0, total_steps - max(1, int(total_steps * 0.10))) return MTP_DECAY_WEIGHT if step >= decay_start else MTP_PRETRAIN_WEIGHT def depth_boost(step: int) -> float: return 1.0 + 0.75 * math.exp(-step / 900.0) # ------------------------------------------------------------------------------------------------- # Memory-mapped samplers and fused losses # ------------------------------------------------------------------------------------------------- def map_u16(path: Path) -> torch.Tensor: return torch.from_file(str(path), shared=False, size=path.stat().st_size // 2, dtype=torch.uint16) def map_u8(path: Path) -> torch.Tensor: return torch.from_file(str(path), shared=False, size=path.stat().st_size, dtype=torch.uint8) class PretrainSampler: def __init__(self, path: Path, sequence: int, seed: int): self.data = map_u16(path) self.sequence = sequence self.seed = seed self.windows = (len(self.data) - 1) // sequence if self.windows <= 0: raise RuntimeError(f"pretrain cache {path} is too short") self.a = coprime_multiplier(self.windows, seed) self.b = seed % self.windows def batch(self, micro_index: int, batch_size: int) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: rows = [] for row in range(batch_size): global_window = micro_index * batch_size + row window = (self.a * global_window + self.b) % self.windows start = window * self.sequence rows.append(self.data[start:start + self.sequence + 1].to(torch.long)) packed_cpu = torch.stack(rows).pin_memory() packed = packed_cpu.to(DEVICE, non_blocking=True) return packed[:, :-1], packed[:, 1:], torch.ones_like(packed[:, 1:], dtype=torch.bool) def coprime_multiplier(records: int, seed: int) -> int: candidate = (seed | 1) % max(2, records) candidate = max(1, candidate) while math.gcd(candidate, records) != 1: candidate += 2 return candidate class RecordSampler: def __init__(self, token_path: Path, mask_path: Path, seed: int): self.record_len = CFG.seq_len + 1 self.tokens = map_u16(token_path).view(-1, self.record_len) self.masks = map_u8(mask_path).view(-1, self.record_len) self.records = self.tokens.shape[0] self.a = coprime_multiplier(self.records, seed) self.b = seed % self.records def indices(self, micro_index: int, batch_size: int) -> list[int]: base = micro_index * batch_size return [(self.a * (base + row) + self.b) % self.records for row in range(batch_size)] def batch(self, micro_index: int, batch_size: int) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: index = torch.tensor(self.indices(micro_index, batch_size), dtype=torch.long) packed_cpu = self.tokens[index].to(torch.long).pin_memory() masks_cpu = self.masks[index, 1:].to(torch.bool).pin_memory() packed = packed_cpu.to(DEVICE, non_blocking=True) masks = masks_cpu.to(DEVICE, non_blocking=True) return packed[:, :-1], packed[:, 1:].to(DEVICE, non_blocking=True), masks class PreferenceSampler: def __init__(self, seed: int): self.record_len = CFG.seq_len + 1 self.chosen = map_u16(PREF_FILES["chosen_tokens"]).view(-1, self.record_len) self.chosen_mask = map_u8(PREF_FILES["chosen_mask"]).view(-1, self.record_len) self.rejected = map_u16(PREF_FILES["rejected_tokens"]).view(-1, self.record_len) self.rejected_mask = map_u8(PREF_FILES["rejected_mask"]).view(-1, self.record_len) self.records = self.chosen.shape[0] self.a = coprime_multiplier(self.records, seed) self.b = seed % self.records def batch(self, micro_index: int, batch_size: int): base = micro_index * batch_size indices = [(self.a * (base + row) + self.b) % self.records for row in range(batch_size)] index = torch.tensor(indices, dtype=torch.long) chosen = self.chosen[index].to(torch.long).pin_memory().to(DEVICE, non_blocking=True) rejected = self.rejected[index].to(torch.long).pin_memory().to(DEVICE, non_blocking=True) chosen_mask = self.chosen_mask[index, 1:].to(torch.bool).pin_memory().to(DEVICE, non_blocking=True) rejected_mask = self.rejected_mask[index, 1:].to(torch.bool).pin_memory().to(DEVICE, non_blocking=True) return chosen, chosen_mask, rejected, rejected_mask class InterleavedSampler: """Low-cost replay that reduces forgetting between curriculum stages.""" def __init__(self, primary, replay, replay_every: int): self.primary, self.replay, self.replay_every = primary, replay, replay_every def batch(self, micro_index: int, batch_size: int): if micro_index % self.replay_every == self.replay_every - 1: return self.replay.batch(micro_index, batch_size) return self.primary.batch(micro_index, batch_size) class CudaBatchPrefetcher: """Double-buffer deterministic batches on a dedicated CUDA copy stream.""" def __init__(self, sampler, batch_size: int, first_micro_index: int): self.sampler = sampler self.batch_size = batch_size self.stream = torch.cuda.Stream() self.next_batch = None self._preload(first_micro_index) def _preload(self, micro_index: int) -> None: with torch.cuda.stream(self.stream): self.next_batch = self.sampler.batch(micro_index, self.batch_size) def get(self, next_micro_index: int): current_stream = torch.cuda.current_stream() current_stream.wait_stream(self.stream) batch = self.next_batch if batch is None: raise RuntimeError("CUDA prefetcher has no prepared batch") for tensor in batch: tensor.record_stream(current_stream) self._preload(next_micro_index) return batch class FusedTokenLoss: def __init__(self): try: from liger_kernel.transformers import LigerFusedLinearCrossEntropyLoss self.liger = LigerFusedLinearCrossEntropyLoss() print("loss kernel: Liger fused linear cross-entropy") except Exception as error: print(f"loss kernel: chunked PyTorch fallback ({type(error).__name__}: {error})") self.liger = None def __call__(self, model: CubicHierLM, hidden: torch.Tensor, targets: torch.Tensor, mask: torch.Tensor) -> torch.Tensor: selected_hidden = hidden.reshape(-1, hidden.shape[-1])[mask.reshape(-1)] selected_targets = targets.reshape(-1)[mask.reshape(-1)] if not selected_targets.numel(): raise RuntimeError("batch has no supervised tokens") if self.liger is not None: return self.liger(model.embed.weight, selected_hidden, selected_targets) total = torch.zeros((), device=hidden.device, dtype=torch.float32) chunk = 2_048 for start in range(0, selected_targets.numel(), chunk): end = min(selected_targets.numel(), start + chunk) logits = F.linear(selected_hidden[start:end], model.embed.weight).float() total = total + F.cross_entropy(logits, selected_targets[start:end], reduction="sum") return total / selected_targets.numel() def response_logps(model: CubicHierLM, hidden: torch.Tensor, tokens: torch.Tensor, mask: torch.Tensor) -> torch.Tensor: """Length-normalized response log-probability for each item in a DPO batch.""" targets = tokens[:, 1:] values = [] for row in range(tokens.shape[0]): selected_hidden = hidden[row][mask[row]] selected_targets = targets[row][mask[row]] if not selected_targets.numel(): raise RuntimeError("DPO row has no response tokens") pieces = [] for start in range(0, selected_targets.numel(), 512): end = min(selected_targets.numel(), start + 512) logits = F.linear(selected_hidden[start:end], model.embed.weight).float() pieces.append(logits.gather(1, selected_targets[start:end, None]).squeeze(1) - logits.logsumexp(-1)) values.append(torch.cat(pieces).mean()) return torch.stack(values) # ------------------------------------------------------------------------------------------------- # Resume-safe staged training # ------------------------------------------------------------------------------------------------- @dataclass(frozen=True) class Phase: name: str steps: int micro_batch: int accumulation: int lr_multiplier: float warmup: int def phase_plan(meta: dict[str, int]) -> list[Phase]: effective_pretrain = MICRO_BATCH * GRAD_ACCUM * CFG.seq_len pretrain_steps = math.ceil(PRETRAIN_TOKENS / effective_pretrain) sft_steps = math.ceil(meta["sft_records"] * SFT_EPOCHS / (MICRO_BATCH * GRAD_ACCUM)) reason_steps = math.ceil(meta["reason_records"] * REASON_EPOCHS / (MICRO_BATCH * GRAD_ACCUM)) dpo_steps = math.ceil(meta["preference_records"] * DPO_EPOCHS / (DPO_MICRO_BATCH * DPO_ACCUM)) limit = env_int("CUBIC_MAX_STEPS", 0) def limited(value: int) -> int: return min(value, limit) if limit > 0 else value return [ Phase("pretrain", limited(pretrain_steps), MICRO_BATCH, GRAD_ACCUM, 1.00, min(2_000, max(100, pretrain_steps // 50))), Phase("dialogue_sft", limited(sft_steps), MICRO_BATCH, GRAD_ACCUM, 0.30, min(500, max(50, sft_steps // 20))), Phase("reasoning_sft", limited(reason_steps), MICRO_BATCH, GRAD_ACCUM, 0.20, min(300, max(30, reason_steps // 20))), Phase("dpo", limited(dpo_steps), DPO_MICRO_BATCH, DPO_ACCUM, 0.05, min(200, max(20, dpo_steps // 20))), ] def cpu_model_state(model: nn.Module) -> dict[str, torch.Tensor]: return {name: value.detach().cpu() for name, value in model.state_dict().items()} def first_nonfinite_tensor(state: Any, prefix: str = "") -> str | None: if isinstance(state, torch.Tensor): if (state.is_floating_point() or state.is_complex()) and not bool( torch.isfinite(state).all() ): return prefix or "" return None if isinstance(state, dict): items = state.items() elif isinstance(state, (list, tuple)): items = enumerate(state) else: return None for name, value in items: child = f"{prefix}.{name}" if prefix else str(name) bad = first_nonfinite_tensor(value, child) if bad is not None: return bad return None @torch.no_grad() def first_nonfinite_parameter(model: nn.Module) -> str | None: for name, parameter in model.named_parameters(): if not bool(torch.isfinite(parameter).all()): return name return None def atomic_torch_save(payload: dict[str, Any], path: Path) -> None: temporary = path.with_suffix(path.suffix + ".tmp") torch.save(payload, temporary) os.replace(temporary, path) class HourlyCheckpointPublisher: """Upload immutable hourly resume snapshots without stalling the GPU.""" def __init__(self) -> None: self.enabled = HF_CHECKPOINT_UPLOAD self.interval = HF_CHECKPOINT_MINUTES * 60.0 self.last_started = time.monotonic() self.thread: threading.Thread | None = None self.lock = threading.Lock() self.snapshot_root = CHECKPOINTS / ".hf_hourly_upload" @staticmethod def _snapshot_copy(source: Path, destination: Path) -> None: destination.parent.mkdir(parents=True, exist_ok=True) try: subprocess.run([ "cp", "--reflink=auto", "--sparse=always", "--preserve=timestamps", "--", str(source), str(destination), ], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) except (FileNotFoundError, subprocess.CalledProcessError): shutil.copy2(source, destination) def _upload(self, folder: Path, phase_index: int, step: int) -> None: try: from huggingface_hub import HfApi api = HfApi(token=os.environ.get("HF_TOKEN") or None) api.create_repo( HF_CHECKPOINT_REPO, repo_type="model", private=False, exist_ok=True, ) api.upload_folder( repo_id=HF_CHECKPOINT_REPO, repo_type="model", folder_path=str(folder), commit_message=( f"Hourly Cubic resume checkpoint: phase {phase_index}, step {step}" ), ) print( f"\nHF CHECKPOINT UPLOADED: https://huggingface.co/" f"{HF_CHECKPOINT_REPO} phase={phase_index} step={step}", flush=True, ) except Exception as exc: # Training must never die because the network or Hub is unavailable. print( f"\nWARNING: hourly Hugging Face checkpoint upload failed: {exc!r}. " "Training continues; the next hourly attempt will retry.", flush=True, ) finally: shutil.rmtree(folder, ignore_errors=True) def maybe_publish( self, checkpoint_path: Path, phase_index: int, step: int, force: bool = False, ) -> bool: if not self.enabled or not checkpoint_path.is_file(): return False with self.lock: if self.thread is not None and self.thread.is_alive(): if force: self.thread.join() else: print("Hourly HF upload is still active; skipping overlapping upload.") return False now = time.monotonic() if not force and now - self.last_started < self.interval: return False self.last_started = now stamp = f"phase-{phase_index:02d}-step-{step:08d}-{int(time.time())}" folder = self.snapshot_root / stamp checkpoint_copy = folder / "checkpoints" / "latest.pt" self._snapshot_copy(checkpoint_path, checkpoint_copy) self._snapshot_copy(TOKENIZER_PATH, folder / "work" / "tokenizer.json") script_path = Path(__file__).resolve() if script_path.is_file(): self._snapshot_copy(script_path, folder / "train_and_continue.py") status = { "format": ARCH_VERSION, "phase_index": phase_index, "next_step": step, "checkpoint": "checkpoints/latest.pt", "uploaded_at": time.strftime("%Y-%m-%dT%H:%M:%S%z"), } atomic_json(status, folder / "hourly_checkpoint.json") self.thread = threading.Thread( target=self._upload, args=(folder, phase_index, step), name="cubic-hf-checkpoint-upload", daemon=True, ) self.thread.start() print( f"Hourly HF checkpoint upload started in background: " f"{HF_CHECKPOINT_REPO} phase={phase_index} step={step}", flush=True, ) return True def finish(self, checkpoint_path: Path, phase_index: int, step: int) -> None: if not self.enabled: return existing = self.thread if existing is not None and existing.is_alive(): print("Waiting for active Hugging Face checkpoint upload ...", flush=True) existing.join() self.maybe_publish(checkpoint_path, phase_index, step, force=True) if self.thread is not None: self.thread.join() HOURLY_CHECKPOINT_PUBLISHER = HourlyCheckpointPublisher() def save_checkpoint( model: CubicHierLM, optimizer: CubicMuon | None, phase_index: int, step: int, metrics: dict[str, Any], path: Path = LATEST, ) -> None: bad_parameter = first_nonfinite_parameter(model) if bad_parameter is not None: # Never replace a resumable checkpoint with poisoned weights. raise FloatingPointError( f"Refusing to save non-finite checkpoint; bad parameter: {bad_parameter}. " f"The previous checkpoint at {path} remains intact." ) payload: dict[str, Any] = { "format": ARCH_VERSION, "config": asdict(CFG), "model": cpu_model_state(model), "phase_index": phase_index, "step": step, "metrics": metrics, "torch_rng": torch.get_rng_state(), "cuda_rng": torch.cuda.get_rng_state_all(), "saved_at": time.strftime("%Y-%m-%dT%H:%M:%S%z"), } if optimizer is not None: payload["muon_optimizer"] = optimizer.state_dict() payload["adamw_optimizer"] = optimizer.adamw.state_dict() bad_state = first_nonfinite_tensor(payload) if bad_state is not None: raise FloatingPointError( f"Refusing to save non-finite training state: {bad_state}. " f"The previous checkpoint at {path} remains intact." ) atomic_torch_save(payload, path) print(f"checkpoint={path} phase_index={phase_index} next_step={step}") if path.resolve() == LATEST.resolve(): HOURLY_CHECKPOINT_PUBLISHER.maybe_publish(path, phase_index, step) def fetch_hub_resume() -> Path | None: """Restore latest.pt and tokenizer.json from the hourly resume repository. A destroyed container loses /marimo but not the hourly upload, so this is the only path back to an eight-hour run. The copy is written next to the local checkpoints under a distinct name and never overwrites latest.pt. """ if not HF_RESUME_DOWNLOAD: return None if os.environ.get("CUBIC_RESUME", "auto").strip().lower() in {"none", "off", "0", "false"}: return None try: from huggingface_hub import snapshot_download snapshot = Path(snapshot_download( repo_id=HF_CHECKPOINT_REPO, repo_type="model", token=os.environ.get("HF_TOKEN") or None, allow_patterns=[ "checkpoints/latest.pt", "work/tokenizer.json", "hourly_checkpoint.json", ], )) except Exception as exc: print(f"Hub resume unavailable ({HF_CHECKPOINT_REPO}): {exc!r}", flush=True) return None status: dict[str, Any] = {} status_path = snapshot / "hourly_checkpoint.json" if status_path.is_file(): try: status = json.loads(status_path.read_text(encoding="utf-8")) except (OSError, ValueError): status = {} if status.get("format") not in (None, ARCH_VERSION): print( f"Hub resume ignored: format={status.get('format')!r}, expected {ARCH_VERSION!r}", flush=True, ) return None remote_tokenizer = snapshot / "work" / "tokenizer.json" if remote_tokenizer.is_file() and not TOKENIZER_PATH.exists(): TOKENIZER_PATH.parent.mkdir(parents=True, exist_ok=True) shutil.copy2(remote_tokenizer, TOKENIZER_PATH) print(f"Restored tokenizer from {HF_CHECKPOINT_REPO}", flush=True) remote_checkpoint = snapshot / "checkpoints" / "latest.pt" if not remote_checkpoint.is_file(): print(f"No checkpoints/latest.pt in {HF_CHECKPOINT_REPO}", flush=True) return None CHECKPOINTS.mkdir(parents=True, exist_ok=True) local = CHECKPOINTS / "hub_latest.pt" if not local.is_file() or local.stat().st_size != remote_checkpoint.stat().st_size: shutil.copy2(remote_checkpoint, local) print( f"Hub resume ready: {local} " f"(next_step={status.get('next_step', '?')}, uploaded_at={status.get('uploaded_at', '?')})", flush=True, ) return local def find_resume_checkpoint() -> Path | None: """Resolve an explicit checkpoint or find the newest resumable checkpoint. CUBIC_RESUME may point to a .pt file or to a directory. Automatic mode searches the active root first and then the common /marimo locations, so changing a launch directory or CUBIC_ROOT cannot silently restart training. Set CUBIC_RESUME=none to deliberately start from scratch. """ requested = os.environ.get("CUBIC_RESUME", "auto").strip() if requested.lower() in {"none", "off", "0", "false"}: print("Checkpoint resume explicitly disabled (CUBIC_RESUME=none).") return None if requested.lower() != "auto": path = Path(requested).expanduser() if path.is_dir(): candidates = list(path.glob("*.pt")) + list(path.glob("checkpoints/*.pt")) if not candidates: raise FileNotFoundError(f"No .pt checkpoints under CUBIC_RESUME={path}") return max(candidates, key=lambda item: item.stat().st_mtime) if not path.is_file(): raise FileNotFoundError(f"CUBIC_RESUME checkpoint does not exist: {path}") return path candidates: list[Path] = [] search_dirs = [CHECKPOINTS, ROOT, Path("/marimo/checkpoints"), Path("/marimo")] seen: set[Path] = set() for folder in search_dirs: if not folder.exists(): continue # Avoid a costly recursive walk through datasets: only known checkpoint # directories are recursive; /marimo itself is searched one level deep. patterns = ("*.pt", "checkpoints/*.pt") if folder == Path("/marimo") else ("*.pt",) for pattern in patterns: for path in folder.glob(pattern): resolved = path.resolve() if resolved not in seen and not path.name.endswith(".tmp"): seen.add(resolved) candidates.append(path) if not candidates: hub = fetch_hub_resume() if hub is not None: return hub print(f"No checkpoint found; starting from step 0. Expected: {LATEST}") return None # Prefer latest.pt when present in the active root; otherwise newest file. return LATEST if LATEST.is_file() else max(candidates, key=lambda item: item.stat().st_mtime) def validated_checkpoint(path: Path) -> dict[str, Any]: payload = torch.load(path, map_location="cpu", weights_only=False) if payload.get("format") != ARCH_VERSION: raise RuntimeError(f"format={payload.get('format')!r}, expected={ARCH_VERSION!r}") bad_tensor = first_nonfinite_tensor(payload.get("model", {})) if bad_tensor is not None: raise FloatingPointError(f"non-finite model tensor {bad_tensor}") for optimizer_key in ("muon_optimizer", "adamw_optimizer"): if optimizer_key in payload: bad_tensor = first_nonfinite_tensor(payload[optimizer_key], optimizer_key) if bad_tensor is not None: raise FloatingPointError(f"non-finite optimizer tensor {bad_tensor}") return payload def load_checkpoint(model: CubicHierLM) -> tuple[int, int, dict[str, Any], dict[str, Any] | None]: checkpoint_path = find_resume_checkpoint() if checkpoint_path is None: return 0, 0, {}, None candidates = [checkpoint_path] if BEST.is_file() and BEST.resolve() != checkpoint_path.resolve(): candidates.append(BEST) payload = None loaded_path = None failures = [] hub_tried = False position = 0 while position < len(candidates): candidate = candidates[position] position += 1 print(f"Checking resume checkpoint {candidate} ...", flush=True) try: payload, loaded_path = validated_checkpoint(candidate), candidate break except Exception as exc: failures.append(f"{candidate}: {exc!r}") print(f" rejected checkpoint: {exc!r}", flush=True) if position >= len(candidates) and not hub_tried: # Only reach for the network once every local copy has failed. hub_tried = True hub_candidate = fetch_hub_resume() known = {item.resolve() for item in candidates} if hub_candidate is not None and hub_candidate.resolve() not in known: print("Local checkpoints all failed; trying the Hub copy ...", flush=True) candidates.append(hub_candidate) if payload is None or loaded_path is None: message = ( "No finite compatible checkpoint is available:\n " + "\n ".join(failures) ) if not ALLOW_COLD_START: raise RuntimeError( message + "\nRefusing to discard existing training progress by silently restarting " "from step 0. Inspect the files above, or set CUBIC_ALLOW_COLD_START=1 " "to deliberately begin a fresh run." ) print(message, flush=True) print("CUBIC_ALLOW_COLD_START=1: starting from step 0.", flush=True) return 0, 0, {}, None print(f"Resuming from verified checkpoint {loaded_path} ...", flush=True) saved_config = payload.get("config", {}) structural_fields = ("vocab_size", "dim", "depth", "n_heads", "mlp_hidden", "depth_rank", "depth_heads") mismatches = { key: (saved_config.get(key), getattr(CFG, key)) for key in structural_fields if key in saved_config and saved_config[key] != getattr(CFG, key) } if mismatches: raise RuntimeError(f"checkpoint architecture is incompatible: {mismatches}") model.load_state_dict(payload["model"], strict=True) if "torch_rng" in payload: torch.set_rng_state(payload["torch_rng"]) if "cuda_rng" in payload and torch.cuda.is_available(): torch.cuda.set_rng_state_all(payload["cuda_rng"]) phase_index, step = int(payload["phase_index"]), int(payload["step"]) print( f"Checkpoint loaded successfully: phase_index={phase_index}, next_step={step}, " f"saved_at={payload.get('saved_at', 'unknown')}", flush=True, ) return phase_index, step, payload.get("metrics", {}), payload def compile_model(model: CubicHierLM): if not COMPILE: return model valid_modes = { "default", "reduce-overhead", "max-autotune", "max-autotune-no-cudagraphs", } if COMPILE_MODE not in valid_modes: raise ValueError( f"Unsupported CUBIC_COMPILE_MODE={COMPILE_MODE!r}; " f"choose one of {sorted(valid_modes)}" ) print( f"Compiling faithful CubicV7-Hier + QKNorm/MTP graph " f"(mode={COMPILE_MODE}; first step will be slow) ..." ) return torch.compile(model, mode=COMPILE_MODE, dynamic=False) @torch.no_grad() def evaluate_lm( run_model, model: CubicHierLM, sampler, loss_fn: FusedTokenLoss, batches: int = EVAL_BATCHES, batch_size: int = EVAL_BATCH_SIZE, ) -> float: model.eval() previous_checkpointing = model.gradient_checkpointing model.gradient_checkpointing = False losses = [] for index in range(batches): tokens, targets, mask = sampler.batch(9_000_000 + index, batch_size) with amp_context(): hidden = run_model(tokens) loss = loss_fn(model, hidden, targets, mask) losses.append(float(loss)) model.gradient_checkpointing = previous_checkpointing model.train() return sum(losses) / len(losses) def train_language_phase( phase_index: int, phase: Phase, model: CubicHierLM, run_model, sampler, loss_fn: FusedTokenLoss, start_step: int, resume_payload: dict[str, Any] | None, metrics: dict[str, Any], val_sampler, ) -> dict[str, Any]: optimizer = build_optimizer(model, phase.lr_multiplier) if resume_payload and start_step and "muon_optimizer" in resume_payload: optimizer.load_state_dict(resume_payload["muon_optimizer"]) optimizer.adamw.load_state_dict(resume_payload["adamw_optimizer"]) print(f"restored optimizer at {phase.name} step {start_step}") model.train() started = time.perf_counter() last_checkpoint = time.monotonic() running_loss = running_lm_loss = running_mtp_loss = 0.0 tokens_seen = 0 first_micro_index = start_step * phase.accumulation prefetcher = CudaBatchPrefetcher(sampler, phase.micro_batch, first_micro_index) lr_backoff = 1.0 consecutive_nonfinite = 0 for step in range(start_step, phase.steps): optimizer.zero_grad(set_to_none=True) step_loss = step_lm_loss = step_mtp_loss = 0.0 invalid_reason = None mtp_weight = mtp_loss_weight(phase.name, step, phase.steps) for accumulation_index in range(phase.accumulation): micro_index = step * phase.accumulation + accumulation_index tokens, targets, mask = prefetcher.get(micro_index + 1) with amp_context(): hidden = run_model(tokens) lm_loss = loss_fn(model, hidden, targets, mask) mtp_mask = mask[:, 1:] mtp_hidden = model.mtp_conditioned_hidden(hidden[:, :-1], tokens[:, 1:]) mtp_loss = loss_fn(model, mtp_hidden, targets[:, 1:], mtp_mask) loss = lm_loss + mtp_weight * mtp_loss scaled_loss = loss / phase.accumulation loss_value = float(loss.detach()) lm_value = float(lm_loss.detach()) mtp_value = float(mtp_loss.detach()) if not ( math.isfinite(loss_value) and math.isfinite(lm_value) and math.isfinite(mtp_value) ): invalid_reason = ( f"non-finite loss: total={loss_value}, " f"lm={lm_value}, mtp={mtp_value}" ) break scaled_loss.backward() step_loss += loss_value / phase.accumulation step_lm_loss += lm_value / phase.accumulation step_mtp_loss += mtp_value / phase.accumulation tokens_seen += int(mask.numel() if phase.name == "pretrain" else mask.sum()) if invalid_reason is None: total_grad_norm = torch.nn.utils.clip_grad_norm_( model.parameters(), GRAD_CLIP, foreach=True, ) grad_norm_value = float(total_grad_norm.detach()) if not math.isfinite(grad_norm_value): invalid_reason = f"non-finite gradient norm: {grad_norm_value}" if invalid_reason is not None: optimizer.zero_grad(set_to_none=True) consecutive_nonfinite += 1 lr_backoff = max(0.125, lr_backoff * 0.5) # Re-align double buffering if an accumulation cycle was aborted. prefetcher = CudaBatchPrefetcher( sampler, phase.micro_batch, (step + 1) * phase.accumulation, ) print( f"\nSTABILITY GUARD: skipped {phase.name} step {step + 1}: " f"{invalid_reason}; lr_backoff={lr_backoff:.3f}", flush=True, ) if consecutive_nonfinite >= 8: raise FloatingPointError( "Eight consecutive non-finite steps were blocked. " "The last finite checkpoint was preserved; stopping safely." ) continue consecutive_nonfinite = 0 scale = ( wsd_schedule(step, phase.steps, phase.warmup) if phase.name == "pretrain" else cosine_schedule(step, phase.steps, phase.warmup) ) optimizer.set_lr_scale( scale * lr_backoff, gate_schedule(step, scale) * lr_backoff, depth_boost(step), ) optimizer.step() lr_backoff = min(1.0, lr_backoff * 1.002) running_loss += step_loss running_lm_loss += step_lm_loss running_mtp_loss += step_mtp_loss completed = step + 1 if completed == 1 or completed % LOG_EVERY == 0: elapsed = time.perf_counter() - started average = running_loss / (1 if completed == 1 else LOG_EVERY) print( f"[{phase.name}] {completed:7d}/{phase.steps} loss={average:.4f} " f"lm={running_lm_loss / (1 if completed == 1 else LOG_EVERY):.4f} " f"mtp={running_mtp_loss / (1 if completed == 1 else LOG_EVERY):.4f}@{mtp_weight:.2f} " f"lr={scale * lr_backoff:.4f} grad={grad_norm_value:.3f} " f"tok/s={tokens_seen / max(elapsed, 1e-6):,.0f} elapsed={elapsed / 60:.1f}m", flush=True, ) running_loss = running_lm_loss = running_mtp_loss = 0.0 if completed % EVAL_EVERY == 0 or completed == phase.steps: validation_loss = evaluate_lm(run_model, model, val_sampler, loss_fn) metrics[f"{phase.name}_step_{completed}"] = { "validation_loss": validation_loss, "perplexity": math.exp(min(20.0, validation_loss)), } print(f" validation loss={validation_loss:.4f} ppl={math.exp(min(20, validation_loss)):.2f}") best_loss = float(metrics.get("best_validation_loss", float("inf"))) if validation_loss < best_loss: metrics["best_validation_loss"] = validation_loss # Best is a fully resumable last-known-good fallback, including # both Muon and AdamW states—not model weights alone. save_checkpoint(model, optimizer, phase_index, completed, metrics, BEST) if time.monotonic() - last_checkpoint >= CHECKPOINT_MINUTES * 60: save_checkpoint(model, optimizer, phase_index, completed, metrics) last_checkpoint = time.monotonic() save_checkpoint(model, None, phase_index + 1, 0, metrics) del optimizer gc.collect() torch.cuda.empty_cache() return metrics def train_dpo_phase( phase_index: int, phase: Phase, model: CubicHierLM, run_model, sampler: PreferenceSampler, start_step: int, resume_payload: dict[str, Any] | None, metrics: dict[str, Any], ) -> dict[str, Any]: print("Loading/freezing the immutable post-reasoning DPO reference ...") reference = copy.deepcopy(model).to(DEVICE).eval().requires_grad_(False) if start_step > 0: if not DPO_REFERENCE.exists(): raise RuntimeError("Cannot resume DPO: immutable reference checkpoint is missing") reference_payload = torch.load(DPO_REFERENCE, map_location="cpu", weights_only=False) reference.load_state_dict(reference_payload["model"], strict=True) else: atomic_torch_save({ "format": ARCH_VERSION + "-dpo-reference", "config": asdict(CFG), "model": cpu_model_state(reference), }, DPO_REFERENCE) reference.gradient_checkpointing = False optimizer = build_optimizer(model, phase.lr_multiplier) if resume_payload and start_step and "muon_optimizer" in resume_payload: optimizer.load_state_dict(resume_payload["muon_optimizer"]) optimizer.adamw.load_state_dict(resume_payload["adamw_optimizer"]) model.train() started = time.perf_counter() last_checkpoint = time.monotonic() running_loss = running_margin = 0.0 first_micro_index = start_step * phase.accumulation prefetcher = CudaBatchPrefetcher(sampler, phase.micro_batch, first_micro_index) lr_backoff = 1.0 consecutive_nonfinite = 0 for step in range(start_step, phase.steps): optimizer.zero_grad(set_to_none=True) step_loss = step_margin = 0.0 invalid_reason = None for accumulation_index in range(phase.accumulation): micro_index = step * phase.accumulation + accumulation_index chosen, chosen_mask, rejected, rejected_mask = prefetcher.get(micro_index + 1) combined = torch.cat((chosen, rejected), dim=0) combined_mask = torch.cat((chosen_mask, rejected_mask), dim=0) inputs = combined[:, :-1] with amp_context(): policy_hidden = run_model(inputs) policy_logps = response_logps(model, policy_hidden, combined, combined_mask) with torch.no_grad(): reference_hidden = reference(inputs) reference_logps = response_logps(reference, reference_hidden, combined, combined_mask) batch = chosen.shape[0] policy_margin = policy_logps[:batch] - policy_logps[batch:] reference_margin = reference_logps[:batch] - reference_logps[batch:] preference_logits = DPO_BETA * (policy_margin - reference_margin) dpo_loss = -F.logsigmoid(preference_logits).mean() # Small chosen-response NLL anchor prevents terse preference # optimization from erasing the SFT model's language quality. sft_anchor = -policy_logps[:batch].mean() loss = dpo_loss + DPO_SFT_WEIGHT * sft_anchor loss_value = float(loss.detach()) margin_value = float(policy_margin.detach().mean()) if not math.isfinite(loss_value) or not math.isfinite(margin_value): invalid_reason = ( f"non-finite DPO values: loss={loss_value}, " f"margin={margin_value}" ) break (loss / phase.accumulation).backward() step_loss += loss_value / phase.accumulation step_margin += margin_value / phase.accumulation if invalid_reason is None: total_grad_norm = torch.nn.utils.clip_grad_norm_( model.parameters(), GRAD_CLIP, foreach=True, ) grad_norm_value = float(total_grad_norm.detach()) if not math.isfinite(grad_norm_value): invalid_reason = f"non-finite DPO gradient norm: {grad_norm_value}" if invalid_reason is not None: optimizer.zero_grad(set_to_none=True) consecutive_nonfinite += 1 lr_backoff = max(0.125, lr_backoff * 0.5) prefetcher = CudaBatchPrefetcher( sampler, phase.micro_batch, (step + 1) * phase.accumulation, ) print( f"\nSTABILITY GUARD: skipped DPO step {step + 1}: " f"{invalid_reason}; lr_backoff={lr_backoff:.3f}", flush=True, ) if consecutive_nonfinite >= 8: raise FloatingPointError( "Eight consecutive non-finite DPO steps were blocked; " "the last finite checkpoint remains intact." ) continue consecutive_nonfinite = 0 scale = cosine_schedule(step, phase.steps, phase.warmup) optimizer.set_lr_scale( scale * lr_backoff, gate_schedule(step, scale) * lr_backoff, 1.0, ) optimizer.step() lr_backoff = min(1.0, lr_backoff * 1.002) running_loss += step_loss running_margin += step_margin completed = step + 1 if completed == 1 or completed % LOG_EVERY == 0: divisor = 1 if completed == 1 else LOG_EVERY print( f"[dpo] {completed:7d}/{phase.steps} loss={running_loss / divisor:.4f} " f"chosen_margin={running_margin / divisor:+.4f} elapsed={(time.perf_counter()-started)/60:.1f}m", flush=True, ) running_loss = running_margin = 0.0 if time.monotonic() - last_checkpoint >= CHECKPOINT_MINUTES * 60: metrics[f"dpo_step_{completed}"] = {"loss": step_loss, "chosen_margin": step_margin} save_checkpoint(model, optimizer, phase_index, completed, metrics) last_checkpoint = time.monotonic() metrics["dpo_final"] = {"loss": step_loss, "chosen_margin": step_margin} save_checkpoint(model, None, phase_index + 1, 0, metrics) del optimizer, reference gc.collect() torch.cuda.empty_cache() return metrics # ------------------------------------------------------------------------------------------------- # Generation, export and Hugging Face publishing # ------------------------------------------------------------------------------------------------- def sample_token(logits: torch.Tensor, temperature: float, top_p: float, top_k: int, generated: Sequence[int]) -> int: logits = logits.float().clone() if generated: counts: dict[int, int] = defaultdict(int) for token in generated[-256:]: counts[int(token)] += 1 for token, count in counts.items(): logits[token] /= 1.08 ** min(4, count) logits /= max(temperature, 1e-4) if top_k > 0: threshold = logits.topk(min(top_k, logits.numel())).values[-1] logits[logits < threshold] = float("-inf") sorted_logits, sorted_indices = logits.sort(descending=True) probabilities = sorted_logits.softmax(-1) cumulative = probabilities.cumsum(-1) remove = cumulative - probabilities > top_p sorted_logits[remove] = float("-inf") selected = torch.multinomial(sorted_logits.softmax(-1), 1) return int(sorted_indices[selected]) @torch.no_grad() def generate( model: CubicHierLM, tokenizer, prompt: str, max_new_tokens: int = 512, temperature: float = 0.60, top_p: float = 0.95, top_k: int = 50, reasoning: bool = False, ) -> str: model.eval() previous_checkpointing = model.gradient_checkpointing model.gradient_checkpointing = False prefix, _ = encode_chat(tokenizer, [ {"role": "system", "content": REASONING_SYSTEM if reasoning else DIRECT_SYSTEM}, {"role": "user", "content": prompt}, ], supervise_assistant=False) prefix.append(tokenizer.token_to_id("<|assistant|>")) generated: list[int] = [] eos = tokenizer.token_to_id("") for _ in range(max_new_tokens): context = (prefix + generated)[-CFG.max_context:] tokens = torch.tensor([context], dtype=torch.long, device=DEVICE) with amp_context(): hidden = model(tokens) logits = model.logits(hidden[:, -1:])[0, -1] token = sample_token(logits, temperature, top_p, top_k, generated) if token == eos: break generated.append(token) model.gradient_checkpointing = previous_checkpointing model.train() return tokenizer.decode(generated, skip_special_tokens=False).strip() def record_samples(model: CubicHierLM, tokenizer, phase: str, max_new_tokens: int = 256) -> None: prompts = [ ("Explain in simple terms why the sky is blue, and mention one common misconception.", False), ("Напиши короткую, но полезную инструкцию: как проверить Python-функцию на граничных случаях?", False), ("A farmer has 17 sheep. All but 9 run away. Think carefully and give the answer.", True), ] with REPORT.open("a", encoding="utf-8") as handle: for prompt, reasoning in prompts: answer = generate( model, tokenizer, prompt, max_new_tokens=max_new_tokens, reasoning=reasoning, ) item = { "phase": phase, "prompt": prompt, "reasoning": reasoning, "answer": answer, "time": time.time(), } handle.write(json.dumps(item, ensure_ascii=False) + "\n") print(f"\n--- {phase} sample ---\nUSER: {prompt}\nASSISTANT: {answer}\n") MODEL_CARD = """--- license: apache-2.0 language: - en - ru pipeline_tag: text-generation tags: - custom-code - cubic-attention - muon - dpo --- # Cubic Hier 150M An experimental bilingual dialogue/reasoning language model with approximately 157M parameters. It keeps the faithful CubicV7 differentiable depth-memory path, uses hierarchical cosine retrieval in the two penultimate attention layers, and finishes with full global causal attention. Every sequence head uses QK RMSNorm. ## Training recipe 1. Base pretraining on a 72/13/10/5 stream of FineWeb-Edu, Russian FineWeb-2, deduplicated CodeParrot Python and FineMath-4+. 2. Assistant-only SFT on Smol-SmolTalk and English/Russian Aya examples. 3. Assistant-only reasoning SFT on the shortest verified-correct traces from OpenR1-Math-220k. 4. DPO with an SFT anchor on Anthropic HH-RLHF and corrected UltraFeedback. The optimizer is the project's hybrid orthogonalized Muon (large matrices and a boosted depth group) plus AdamW for embeddings, norms, scalars and gates. Base training uses warmup-stable-decay and a one-token-ahead MTP auxiliary loss; embeddings, RMSNorm scales and controls are excluded from AdamW decay. SFT teaches explicit direct and `...` system-prompt modes; set `CUBIC_REASONING=1` in chat mode to request the reasoning format. ## Important This is a custom PyTorch architecture, not a drop-in Transformers model. Run `train_and_chat.py` with `CUBIC_MODE=chat`; it downloads/loads all required files and starts an interactive console. The model is small and experimental: verify factual, safety-critical and mathematical answers independently. Each source dataset retains its own license/terms. FineWeb corpora inherit the Common Crawl terms described on their dataset cards; SmolTalk, Aya and OpenR1 are Apache-2.0; HH-RLHF and UltraFeedback use their published dataset licenses. """ def export_model(model: CubicHierLM, tokenizer, metrics: dict[str, Any], phases: Sequence[Phase]) -> Path: from safetensors.torch import save_file if EXPORT.exists(): shutil.rmtree(EXPORT) EXPORT.mkdir(parents=True) state = {name: value.detach().to(torch.bfloat16).cpu().contiguous() for name, value in model.state_dict().items()} save_file(state, str(EXPORT / "model.safetensors"), metadata={ "format": "pt", "architecture": "CubicHierLM", "version": ARCH_VERSION, }) tokenizer.save(str(EXPORT / "tokenizer.json")) config = asdict(CFG) | { "architectures": ["CubicHierLM"], "model_type": "cubic_hier_v7_gemma4", "torch_dtype": "bfloat16", "tie_word_embeddings": True, "parameter_count": sum(parameter.numel() for parameter in model.parameters()), } atomic_json(config, EXPORT / "config.json") atomic_json({ "temperature": 0.60, "top_p": 0.95, "top_k": 50, "repetition_penalty": 1.08, "max_new_tokens": 512, "bos_token": "", "eos_token": "", "pad_token": "", }, EXPORT / "generation_config.json") manifest = { "architecture_version": ARCH_VERSION, "architecture": "faithful CubicV7 + QKNorm + penultimate hierarchical retrieval + final global attention", "config": config, "pretraining_mix": {"web_en": 0.72, "web_ru": 0.13, "code": 0.10, "math": 0.05}, "optimization": { "base_schedule": "warmup-stable-cosine-decay-last-10-percent", "mtp_pretrain_weight": MTP_PRETRAIN_WEIGHT, "mtp_decay_weight": MTP_DECAY_WEIGHT, "mtp_sft_weight": MTP_SFT_WEIGHT, "adamw_no_decay": ["embeddings", "RMSNorm", "biases", "scalar controls"], }, "phases": [asdict(phase) for phase in phases], "metrics": metrics, "datasets": [ "HuggingFaceFW/fineweb-edu:sample-10BT", "HuggingFaceFW/fineweb-2:rus_Cyrl", "codeparrot/codeparrot-clean", "HuggingFaceTB/finemath:finemath-4plus", "HuggingFaceTB/smol-smoltalk", "CohereLabs/aya_dataset", "open-r1/OpenR1-Math-220k:default", "Anthropic/hh-rlhf", "HuggingFaceH4/ultrafeedback_binarized:train_prefs", ], "created_at": time.strftime("%Y-%m-%dT%H:%M:%S%z"), } atomic_json(manifest, EXPORT / "training_manifest.json") (EXPORT / "README.md").write_text(MODEL_CARD, encoding="utf-8") shutil.copy2(Path(__file__).resolve(), EXPORT / "train_and_chat.py") if REPORT.exists(): shutil.copy2(REPORT, EXPORT / "training_samples.jsonl") if os.environ.get("CUBIC_UPLOAD_RESUME", "1") == "1" and FINAL.exists(): shutil.copy2(FINAL, EXPORT / "final_training_checkpoint.pt") print(f"Export ready: {EXPORT}") return EXPORT def upload_export(folder: Path) -> None: from huggingface_hub import HfApi api = HfApi(token=os.environ.get("HF_TOKEN") or None) api.create_repo(HF_REPO, repo_type="model", private=False, exist_ok=True) print(f"Uploading complete export to https://huggingface.co/{HF_REPO} ...") api.upload_folder( repo_id=HF_REPO, repo_type="model", folder_path=str(folder), commit_message="Publish faithful CubicV7-Hier 157M with QKNorm, MTP, SFT, reasoning and DPO", ) print(f"Published: https://huggingface.co/{HF_REPO}") def load_export_for_chat() -> tuple[CubicHierLM, Any]: from huggingface_hub import snapshot_download from safetensors.torch import load_file from tokenizers import Tokenizer local = Path(os.environ.get("CUBIC_MODEL_DIR", str(EXPORT))) if not (local / "model.safetensors").exists(): local = Path(snapshot_download( repo_id=HF_REPO, token=os.environ.get("HF_TOKEN") or None, allow_patterns=["model.safetensors", "tokenizer.json", "config.json"], )) config_data = json.loads((local / "config.json").read_text(encoding="utf-8")) structural = {field: config_data[field] for field in asdict(CFG) if field in config_data} config = Config(**structural) model = CubicHierLM(config).to(DEVICE, dtype=AMP_DTYPE) model.load_state_dict(load_file(str(local / "model.safetensors"), device="cpu"), strict=True) model.gradient_checkpointing = False model.eval() tokenizer = Tokenizer.from_file(str(local / "tokenizer.json")) print(f"Loaded {sum(p.numel() for p in model.parameters()):,} parameters from {local}") return model, tokenizer def chat_main() -> None: setup_runtime() model, tokenizer = load_export_for_chat() print("Interactive Cubic chat. Empty line exits.") while True: try: prompt = input("\nYou> ").strip() except (EOFError, KeyboardInterrupt): break if not prompt: break print("Cubic> ", end="", flush=True) print(generate( model, tokenizer, prompt, max_new_tokens=env_int("CUBIC_MAX_NEW_TOKENS", 512), temperature=env_float("CUBIC_TEMPERATURE", 0.60), top_p=env_float("CUBIC_TOP_P", 0.95), top_k=env_int("CUBIC_TOP_K", 50), reasoning=os.environ.get("CUBIC_REASONING", "0") == "1", )) def train_main() -> None: setup_runtime() if not TOKENIZER_PATH.exists(): fetch_hub_resume() if find_resume_checkpoint() is not None and not TOKENIZER_PATH.exists(): raise RuntimeError( "A resumable checkpoint exists but tokenizer.json is missing. Training a " "fresh tokenizer would silently invalidate every learned embedding. " f"Restore {TOKENIZER_PATH} from work/tokenizer.json in {HF_CHECKPOINT_REPO}." ) tokenizer = train_tokenizer() meta = prepare_everything(tokenizer) phases = phase_plan(meta) print("=" * 100) print("FAITHFUL CUBIC V7-HIER ~157M — GEMMA4/DEEPSEEK/SMOLLM3 IMPROVED PIPELINE") print(f"device={torch.cuda.get_device_name(0)} dtype={AMP_DTYPE} seq={CFG.seq_len:,} max_context={CFG.max_context:,}") print( f"compile={COMPILE_MODE if COMPILE else 'off'} " f"checkpoint_every={CHECKPOINT_EVERY} " f"effective_pretrain_batch={MICRO_BATCH * GRAD_ACCUM}" ) print(f"data={meta}") for index, phase in enumerate(phases): print(f" [{index}] {phase.name}: steps={phase.steps:,} micro={phase.micro_batch} accum={phase.accumulation}") print("=" * 100) model = CubicHierLM(CFG).to(DEVICE) parameter_count = sum(parameter.numel() for parameter in model.parameters()) print(f"parameters={parameter_count:,} ({parameter_count / 1e6:.2f}M)") if parameter_count != EXPECTED_PARAMETER_COUNT: raise RuntimeError( f"architecture drift: got {parameter_count:,} parameters, " f"expected {EXPECTED_PARAMETER_COUNT:,}" ) resume_phase, resume_step, metrics, resume_payload = load_checkpoint(model) run_model = compile_model(model) loss_fn = FusedTokenLoss() pretrain_train = PretrainSampler(PRETRAIN_TOKENS_FILE, CFG.seq_len, SEED + 100) pretrain_val = PretrainSampler(PRETRAIN_VAL_FILE, CFG.seq_len, SEED + 101) dialogue = RecordSampler(SFT_TOKENS_FILE, SFT_MASK_FILE, SEED + 200) reasoning = RecordSampler(REASON_TOKENS_FILE, REASON_MASK_FILE, SEED + 300) preference = PreferenceSampler(SEED + 400) samplers = { "pretrain": pretrain_train, # 10% base-LM replay during dialogue tuning and 20% dialogue replay # during reasoning tuning prevent curriculum-stage forgetting. "dialogue_sft": InterleavedSampler(dialogue, pretrain_train, replay_every=10), "reasoning_sft": InterleavedSampler(reasoning, dialogue, replay_every=5), } validation = {"pretrain": pretrain_val, "dialogue_sft": dialogue, "reasoning_sft": reasoning} for phase_index, phase in enumerate(phases): if phase_index < resume_phase: print(f"Skipping completed phase: {phase.name}") continue start = resume_step if phase_index == resume_phase else 0 payload = resume_payload if phase_index == resume_phase else None print(f"\n{'=' * 36} PHASE {phase_index + 1}: {phase.name.upper()} {'=' * 36}") if phase.name == "dpo": metrics = train_dpo_phase( phase_index, phase, model, run_model, preference, start, payload, metrics, ) else: metrics = train_language_phase( phase_index, phase, model, run_model, samplers[phase.name], loss_fn, start, payload, metrics, validation[phase.name], ) record_samples(model, tokenizer, phase.name) resume_payload = None resume_step = 0 metrics["parameter_count"] = parameter_count metrics["completed_at"] = time.strftime("%Y-%m-%dT%H:%M:%S%z") save_checkpoint(model, None, len(phases), 0, metrics, FINAL) # Ensure the final state reaches the resume repository even if the last # regular hourly boundary was less than an hour ago. HOURLY_CHECKPOINT_PUBLISHER.finish(FINAL, len(phases), 0) export = export_model(model, tokenizer, metrics, phases) if UPLOAD: upload_export(export) print("\nDONE. Model, tokenizer, script, report and checkpoint are complete.") if __name__ == "__main__": if MODE == "chat": chat_main() elif MODE == "train": train_main() else: raise SystemExit("CUBIC_MODE must be 'train' or 'chat'") return if __name__ == "__main__": app.run()