Download bitnet.cpp/train_bitnet_lambda_warmup_1b.py from CMSManhattan/JiRackTernaryPro_1b: direct link, hf CLI and curl.
- Browser
- Download file 15 kB
-
https://huggingface.co/CMSManhattan/JiRackTernaryPro_1b/resolve/main/bitnet.cpp/train_bitnet_lambda_warmup_1b.py
- Command line
-
hf download hf://CMSManhattan/JiRackTernaryPro_1b/bitnet.cpp/train_bitnet_lambda_warmup_1b.py
-
curl -L -o train_bitnet_lambda_warmup_1b.py https://huggingface.co/CMSManhattan/JiRackTernaryPro_1b/resolve/main/bitnet.cpp/train_bitnet_lambda_warmup_1b.py
15 kB
| %%writefile train_jirack_bitnet_1b_lambda_warmup.py | |
| # ============================================================================= | |
| # COPYRIGHT Β© 2025-2026 Konstantin Vladimirovich Grabko. ALL RIGHTS RESERVED. | |
| # CMS Manhattan JiRack Technology β PATENT PENDING | |
| # | |
| # This code is proprietary. | |
| # Personal and non-commercial research use is allowed. | |
| # Any commercial use, derivative works for profit, or distribution | |
| # requires a paid license and 5% royalty. | |
| # | |
| # Unauthorized commercial use is strictly prohibited. | |
| # Contact: grabko@cmsmanhattan.com | |
| # ============================================================================= | |
| # train_jirack_bitnet_1b_lambda_warmup.py | |
| # ============================================================================= | |
| # QAT training with lambda warmup β for the SubLN-equipped, bitnet.cpp- | |
| # compatible architecture (JiRackTernaryPyTorch_1b_bitnet.py). | |
| # | |
| # Same warmup machinery as the 10B script, re-pointed at the 1B model. | |
| # Differences from the 10B version, all deliberate: | |
| # | |
| # [B-1] import from JiRackTernaryPyTorch_1b_bitnet (SubLN architecture). | |
| # [B-2] BASE_CHECKPOINT points at the output of | |
| # load_old_checkpoint_into_bitnet_arch_1b.py β the migrated | |
| # checkpoint where SubLN weights are freshly initialized at 1.0. | |
| # [B-3] BATCH_SIZE raised 2 -> 8 and GRAD_ACCUM lowered 5 -> 2. The 1B | |
| # model is ~10x smaller, so on a 96GB card there is no reason to | |
| # crawl at micro-batch 2. Effective batch stays 16 (was 10). | |
| # [B-4] LAMBDA_WARMUP_STEPS 5000 -> 2000 micro-batches. The 1B weights | |
| # are ALREADY fully ternary-trained (the original had no lambda at | |
| # all β it ran quantized from step one), so this warmup only has to | |
| # settle the new SubLN weights, not re-ternarize from scratch. | |
| # | |
| # Everything else β the sigmoid lambda schedule, resume-by-global-step, | |
| # optimizer state save/restore, lambda-per-accumulation-window, lambda- | |
| # tagged validation β is unchanged. | |
| # | |
| # WHAT TO WATCH, 1B-specific: this run changes TWO things at once, SubLN | |
| # appearing and activation quantization dropping its mean-centering ([C-3] | |
| # in the architecture file). So expect a real loss spike as lambda crosses | |
| # the steep middle of the sigmoid. Judge by val loss @ a given lambda | |
| # trending down shard over shard. If it has not recovered by the end of | |
| # warmup, raise LAMBDA_WARMUP_STEPS (3000-4000) rather than touching LR β | |
| # the sigmoid midpoint is the sensitive part, not the learning rate. | |
| # ============================================================================= | |
| import os | |
| os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True") | |
| import torch | |
| import glob | |
| import re | |
| import gc | |
| import math | |
| import torch.nn as nn | |
| from torch.utils.data import Dataset, DataLoader | |
| from torch.nn.utils.rnn import pad_sequence | |
| from tqdm import tqdm | |
| from transformers.optimization import Adafactor | |
| # [B-1] SubLN / bitnet.cpp-compatible model class | |
| from JiRackTernaryPyTorch_1b_bitnet import JiRackTransformer1B, JiRackConfig1B | |
| # ========================= ΠΠΠ‘Π’Π ΠΠΠΠ ========================= | |
| DATA_DIR = "/content/prepared_sft_data" | |
| OUTPUT_DIR = "/content/JRock_1B_Bitnet_Checkpoints" | |
| # [B-2] output of load_old_checkpoint_into_bitnet_arch_1b.py | |
| BASE_CHECKPOINT = "/content/jirack_1b_bitnet_init.pt" | |
| BATCH_SIZE = 8 # [B-3] 1B fits comfortably; raise further if VRAM allows | |
| GRAD_ACCUM = 2 # [B-3] effective batch = 16 | |
| LR = 4e-5 | |
| VAL_RATIO = 0.05 | |
| # === Lambda Warmup Settings === | |
| # [B-4] 2000 micro-batches = 1000 optimizer steps @ GRAD_ACCUM=2. | |
| # Shorter than the 10B run on purpose: the 1B weights are already ternary, | |
| # this warmup only has to settle the new SubLN. If val loss has not | |
| # recovered by the end of warmup, raise this (3000-4000), not LR. | |
| LAMBDA_WARMUP_STEPS = 2000 # in MICRO-batches | |
| MAX_LAMBDA = 1.0 | |
| SAVE_OPTIMIZER = True | |
| AUTOSAVE_EVERY = 1000 | |
| # ============================================================= | |
| os.makedirs(OUTPUT_DIR, exist_ok=True) | |
| torch.backends.cuda.matmul.allow_tf32 = True | |
| torch.backends.cudnn.allow_tf32 = True | |
| print("π Loading JiRack 1B Ternary (SubLN / bitnet.cpp) + Lambda Warmup...") | |
| config = JiRackConfig1B() | |
| # [B-3] gradient checkpointing OFF for 1B β it trades ~30% speed for memory | |
| # you do not need at this size. Flip to True if you raise BATCH_SIZE a lot. | |
| USE_GRAD_CHECKPOINT = False | |
| model = JiRackTransformer1B(config, use_checkpoint=USE_GRAD_CHECKPOINT) | |
| model.to("cuda") | |
| for block in model.blocks: | |
| block.use_checkpoint = USE_GRAD_CHECKPOINT | |
| print(f"β Gradient checkpointing: {'enabled' if USE_GRAD_CHECKPOINT else 'disabled'}") | |
| optimizer = Adafactor( | |
| model.parameters(), | |
| lr=LR, | |
| eps=(1e-30, 1e-3), | |
| clip_threshold=1.0, | |
| decay_rate=-0.8, | |
| weight_decay=0.0001, | |
| scale_parameter=False, | |
| relative_step=False, | |
| warmup_init=False, | |
| ) | |
| criterion = nn.CrossEntropyLoss(ignore_index=-100) | |
| # ==================== LAMBDA SCHEDULE ==================== | |
| def lambda_schedule(step: int) -> float: | |
| """Sigmoid ramp over LAMBDA_WARMUP_STEPS micro-batches.""" | |
| if step >= LAMBDA_WARMUP_STEPS: | |
| return MAX_LAMBDA | |
| return MAX_LAMBDA / ( | |
| 1.0 + math.exp(-10 * (step - LAMBDA_WARMUP_STEPS / 2) / LAMBDA_WARMUP_STEPS) | |
| ) | |
| def invert_lambda_schedule(lam: float) -> int: | |
| """Reconstruct global_step from a saved lambda (legacy checkpoints | |
| that stored only the model state_dict). Inverse of the sigmoid above.""" | |
| if lam >= MAX_LAMBDA * 0.999: | |
| return LAMBDA_WARMUP_STEPS | |
| if lam <= 1e-6: | |
| return 0 | |
| p = lam / MAX_LAMBDA | |
| x = -math.log(1.0 / p - 1.0) # logit | |
| return int(round(x * LAMBDA_WARMUP_STEPS / 10 + LAMBDA_WARMUP_STEPS / 2)) | |
| # ==================== CHECKPOINT LOAD ==================== | |
| def load_any_checkpoint(path, model, optimizer): | |
| """Handles both new-format dicts and legacy plain state_dicts. | |
| Returns restored global_step. | |
| NOTE: on the very first load (BASE_CHECKPOINT = migration output), | |
| the state_dict has EVERY tensor including attn_sub_norm/ffn_sub_norm | |
| (the migration script already wrote them, at value 1.0), so this is | |
| a normal strict-ish load β no special-casing needed here for SubLN.""" | |
| ckpt = torch.load(path, map_location="cpu", weights_only=True) | |
| if isinstance(ckpt, dict) and "model" in ckpt: | |
| missing, unexpected = model.load_state_dict(ckpt["model"], strict=False) | |
| assert not unexpected, unexpected | |
| assert all(k.endswith("lambda_") for k in missing), missing | |
| if SAVE_OPTIMIZER and "optimizer" in ckpt and ckpt["optimizer"] is not None: | |
| try: | |
| optimizer.load_state_dict(ckpt["optimizer"]) | |
| print("β Optimizer state restored") | |
| except Exception as e: | |
| print(f"β οΈ Optimizer state not restored ({e}); continuing fresh") | |
| step = int(ckpt.get("global_step", 0)) | |
| print(f"β Resumed (new format): global_step={step}, " | |
| f"lambda={ckpt.get('lambda', 'n/a')}") | |
| return step | |
| # legacy: plain state_dict (e.g. straight from load_from_old_checkpoint | |
| # + torch.save, no wrapper dict) β missing keys should only be lambda_ | |
| # buffers here, since the migration script already fills SubLN weights. | |
| missing, unexpected = model.load_state_dict(ckpt, strict=False) | |
| assert not unexpected, unexpected | |
| assert all(k.endswith("lambda_") for k in missing), missing | |
| lam = model.get_lambda() | |
| step = invert_lambda_schedule(lam) | |
| print(f"β Resumed (legacy format): lambda={lam:.4f} -> " | |
| f"reconstructed global_step={step}") | |
| return step | |
| checkpoints = sorted( | |
| glob.glob(os.path.join(OUTPUT_DIR, "jirack_1b_data_*.pt")), | |
| key=lambda x: int(re.search(r"data_(\d+)", x).group(1)), | |
| ) | |
| all_ckpts = glob.glob(os.path.join(OUTPUT_DIR, "*.pt")) | |
| global_step = 0 | |
| if all_ckpts: | |
| LATEST_CKPT = max(all_ckpts, key=os.path.getmtime) | |
| print(f"π¦ Resuming from (newest on disk): {LATEST_CKPT}") | |
| global_step = load_any_checkpoint(LATEST_CKPT, model, optimizer) | |
| else: | |
| print(f"π¦ Loading base (migrated) checkpoint: {BASE_CHECKPOINT}") | |
| assert os.path.exists(BASE_CHECKPOINT), ( | |
| f"{BASE_CHECKPOINT} not found β run " | |
| f"load_old_checkpoint_into_bitnet_arch_1b.py first" | |
| ) | |
| global_step = load_any_checkpoint(BASE_CHECKPOINT, model, optimizer) | |
| # Fresh migration -> starting lambda warmup from scratch, even if the | |
| # checkpoint happened to carry a lambda value from the old run. | |
| global_step = 0 | |
| print("π Fresh SubLN migration detected: lambda warmup restarted from step 0 " | |
| "(SubLN has never seen a nonzero lambda before).") | |
| model.set_lambda(lambda_schedule(global_step)) | |
| print(f"π§ Warmup: {LAMBDA_WARMUP_STEPS} steps | starting at " | |
| f"step={global_step}, lambda={model.get_lambda():.4f}") | |
| # ========================= DATASET ========================= | |
| class ShardDataset(Dataset): | |
| def __init__(self, data_list): | |
| self.data = data_list | |
| def __len__(self): | |
| return len(self.data) | |
| def __getitem__(self, idx): | |
| return self.data[idx] | |
| def collate_fn(batch): | |
| input_ids = pad_sequence( | |
| [item["input_ids"] for item in batch], batch_first=True, padding_value=0 | |
| ) | |
| attention_mask = pad_sequence( | |
| [item.get("attention_mask", torch.ones_like(item["input_ids"])) | |
| for item in batch], | |
| batch_first=True, padding_value=0, | |
| ) | |
| labels = input_ids.clone() | |
| labels[attention_mask == 0] = -100 | |
| return {"input_ids": input_ids, "labels": labels} | |
| # ========================= CHECKPOINT SAVE ========================= | |
| def save_checkpoint(path, model, optimizer, global_step, lam): | |
| model_sd = {k: v.detach().to(torch.bfloat16).cpu() | |
| for k, v in model.state_dict().items()} | |
| ckpt = { | |
| "model": model_sd, | |
| "optimizer": optimizer.state_dict() if SAVE_OPTIMIZER else None, | |
| "global_step": global_step, | |
| "lambda": lam, | |
| } | |
| torch.save(ckpt, path) | |
| # ========================= TRAINING ========================= | |
| all_shards = sorted( | |
| glob.glob(f"{DATA_DIR}/sft_data_*.pt"), | |
| key=lambda x: int(re.search(r"sft_data_(\d+)", x).group(1)), | |
| ) | |
| last_done = -1 | |
| if checkpoints: | |
| m = re.search(r"data_(\d+)", checkpoints[-1]) | |
| last_done = int(m.group(1)) if m else -1 | |
| for shard_path in all_shards: | |
| shard_name = os.path.basename(shard_path) | |
| shard_num = int(re.search(r"sft_data_(\d+)", shard_name).group(1)) | |
| if shard_num <= last_done: | |
| print(f"β Skipping already processed: {shard_name}") | |
| continue | |
| print(f"\nπ₯ Starting shard: {shard_name}") | |
| raw_shard_data = torch.load(shard_path, map_location="cpu", weights_only=False) | |
| val_size = int(len(raw_shard_data) * VAL_RATIO) | |
| train_size = len(raw_shard_data) - val_size | |
| train_data, val_data = torch.utils.data.random_split( | |
| raw_shard_data, [train_size, val_size], | |
| generator=torch.Generator().manual_seed(42), | |
| ) | |
| train_loader = DataLoader( | |
| ShardDataset(train_data), batch_size=BATCH_SIZE, shuffle=True, | |
| collate_fn=collate_fn, pin_memory=True, | |
| ) | |
| model.train() | |
| pbar = tqdm(train_loader, desc=f"Shard {shard_num}", dynamic_ncols=True) | |
| optimizer.zero_grad() | |
| lambda_value = lambda_schedule(global_step) | |
| model.set_lambda(lambda_value) | |
| for step, batch in enumerate(pbar): | |
| if step % GRAD_ACCUM == 0: | |
| lambda_value = lambda_schedule(global_step) | |
| model.set_lambda(lambda_value) | |
| input_ids = batch["input_ids"].to("cuda", non_blocking=True) | |
| labels = batch["labels"].to("cuda", non_blocking=True) | |
| with torch.amp.autocast("cuda", dtype=torch.bfloat16): | |
| logits = model(input_ids) | |
| if isinstance(logits, tuple): | |
| logits = logits[0] | |
| loss = criterion( | |
| logits[..., :-1, :].reshape(-1, config.vocab_size), | |
| labels[..., 1:].reshape(-1), | |
| ) | |
| loss = loss / GRAD_ACCUM | |
| if torch.isnan(loss) or torch.isinf(loss): | |
| print(f"\nβ οΈ NaN/Inf loss at step {global_step} " | |
| f"(lambda={lambda_value:.4f}) β window dropped") | |
| optimizer.zero_grad(set_to_none=True) | |
| torch.cuda.empty_cache() | |
| global_step += 1 | |
| continue | |
| loss.backward() | |
| if (step + 1) % GRAD_ACCUM == 0: | |
| torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) | |
| optimizer.step() | |
| optimizer.zero_grad() | |
| if step % 10 == 0: | |
| pbar.set_postfix({ | |
| "loss": f"{loss.item() * GRAD_ACCUM:.4f}", | |
| "lambda": f"{lambda_value:.4f}", | |
| "gstep": global_step, | |
| }) | |
| if (AUTOSAVE_EVERY > 0 and global_step > 0 | |
| and global_step % AUTOSAVE_EVERY == 0 | |
| and (step + 1) % GRAD_ACCUM == 0): | |
| autosave_path = os.path.join(OUTPUT_DIR, "autosave_latest.pt") | |
| tmp_path = autosave_path + ".tmp" | |
| save_checkpoint(tmp_path, model, optimizer, global_step, lambda_value) | |
| os.replace(tmp_path, autosave_path) | |
| pbar.write(f"πΎ autosave @ gstep={global_step}, " | |
| f"lambda={lambda_value:.4f}") | |
| global_step += 1 | |
| # ==================== Validation ==================== | |
| print("π§ͺ Validating...") | |
| model.eval() | |
| total_val_loss = 0.0 | |
| val_steps = 0 | |
| val_loader = DataLoader( | |
| ShardDataset(val_data), batch_size=BATCH_SIZE, shuffle=False, | |
| collate_fn=collate_fn, pin_memory=True, | |
| ) | |
| with torch.no_grad(): | |
| for batch in tqdm(val_loader, desc="Validating", leave=False): | |
| input_ids = batch["input_ids"].to("cuda", non_blocking=True) | |
| labels = batch["labels"].to("cuda", non_blocking=True) | |
| with torch.amp.autocast("cuda", dtype=torch.bfloat16): | |
| logits = model(input_ids) | |
| if isinstance(logits, tuple): | |
| logits = logits[0] | |
| v_loss = criterion( | |
| logits[..., :-1, :].reshape(-1, config.vocab_size), | |
| labels[..., 1:].reshape(-1), | |
| ) | |
| if not (torch.isnan(v_loss) or torch.isinf(v_loss)): | |
| total_val_loss += v_loss.item() | |
| val_steps += 1 | |
| avg_val_loss = total_val_loss / val_steps if val_steps > 0 else float("inf") | |
| print(f"π Shard {shard_num} β Val Loss: {avg_val_loss:.4f} " | |
| f"@ lambda={lambda_value:.4f} (gstep={global_step})") | |
| save_path = os.path.join(OUTPUT_DIR, f"jirack_1b_data_{shard_num}.pt") | |
| save_checkpoint(save_path, model, optimizer, global_step, lambda_value) | |
| print(f"πΎ Saved: {save_path}") | |
| del raw_shard_data | |
| torch.cuda.empty_cache() | |
| gc.collect() | |
| print("π Training finished with Lambda Warmup (SubLN / bitnet.cpp arch)!") | |