Instructions to use throsturx/bihmoe-poc with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use throsturx/bihmoe-poc with Transformers:
# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("throsturx/bihmoe-poc", device_map="auto") - Notebooks
- Google Colab
- Kaggle
| from __future__ import annotations | |
| import os, json, time | |
| import random | |
| from typing import Dict, List, Tuple, Callable | |
| import yaml | |
| import numpy as np | |
| import torch | |
| import torch.nn.functional as F | |
| from rich.console import Console | |
| from rich.table import Table | |
| from bihmoe.models.dense import DenseModel | |
| from bihmoe.models.structured import StructuredBiHMoE | |
| from bihmoe.utils.misc import set_seed, fmt_bytes | |
| from bihmoe.utils.metrics import accuracy | |
| from bihmoe.render.english import ( | |
| render_bind_query_q, render_bind_query_a, | |
| render_twohop_q, render_twohop_a, | |
| ) | |
| console = Console() | |
| def _fmt(x, nd=4): | |
| try: | |
| if x != x: | |
| return "NaN" | |
| return f"{float(x):.{nd}f}" | |
| except Exception: | |
| return str(x) | |
| def print_metrics_pretty(metrics: dict) -> None: | |
| step = metrics.get("step", "?") | |
| def pair(split: str) -> str: | |
| s_acc = float(metrics.get(f"{split}/acc_s", 0.0)) | |
| d_acc = float(metrics.get(f"{split}/acc_d", 0.0)) | |
| delta = s_acc - d_acc | |
| return f"{split} {s_acc:.3f}/{d_acc:.3f} (Δ {delta:+.3f})" | |
| # One-line summary (corner-of-eye) | |
| line = " ".join([ | |
| f"step={step}", | |
| pair("iid"), | |
| pair("ood"), | |
| pair("ood_long"), | |
| pair("braid"), | |
| pair("pert"), | |
| ]) | |
| console.rule(f"[bold]Step {step}[/bold]") | |
| print("KEYLINE " + line) | |
| console.print(line) | |
| table = Table(show_header=True, header_style="bold") | |
| table.add_column("split") | |
| table.add_column("acc_s", justify="right") | |
| table.add_column("acc_d", justify="right") | |
| table.add_column("loss_s", justify="right") | |
| table.add_column("loss_d", justify="right") | |
| table.add_column("disagree μ", justify="right") | |
| table.add_column("disagree(c)", justify="right") | |
| table.add_column("disagree(!c)", justify="right") | |
| for split in ["iid", "ood", "ood_long", "braid", "pert"]: | |
| table.add_row( | |
| split, | |
| _fmt(metrics.get(f"{split}/acc_s"), 4), | |
| _fmt(metrics.get(f"{split}/acc_d"), 4), | |
| _fmt(metrics.get(f"{split}/loss_s"), 4), | |
| _fmt(metrics.get(f"{split}/loss_d"), 4), | |
| _fmt(metrics.get(f"{split}/disagree_mean"), 4), | |
| _fmt(metrics.get(f"{split}/disagree_correct_mean"), 4), | |
| _fmt(metrics.get(f"{split}/disagree_incorrect_mean"), 4), | |
| ) | |
| console.print(table) | |
| console.print( | |
| f"train loss: S={_fmt(metrics.get('loss_s_train'),4)} D={_fmt(metrics.get('loss_d_train'),4)} " | |
| f"cuda_peak={metrics.get('cuda_peak_h','?')} reserved={metrics.get('cuda_reserved_h','?')}" | |
| ) | |
| def now_run_id(tag: str) -> str: | |
| return time.strftime("%Y%m%d-%H%M%S") + f"_{tag}" | |
| def amp_setup(device: torch.device, mode: str): | |
| mode = str(mode).lower() | |
| if device.type != "cuda": | |
| return (False, None, None) | |
| if mode == "bf16": | |
| try: | |
| if hasattr(torch.cuda, "is_bf16_supported") and not torch.cuda.is_bf16_supported(): | |
| console.print("WARN: bf16 not supported; falling back to fp16 AMP") | |
| mode = "fp16" | |
| except Exception: | |
| mode = "fp16" | |
| if mode == "fp16": | |
| return (True, torch.float16, torch.amp.GradScaler('cuda', enabled=True)) | |
| if mode == "bf16": | |
| return (True, torch.bfloat16, torch.cuda.amp.GradScaler(enabled=False)) | |
| return (False, None, None) | |
| def compute_match_dense(struct_cfg: Dict, round_multiple: int = 256) -> Tuple[int,int]: | |
| Ls = int(struct_cfg["stem_layers"]) | |
| Lh = int(struct_cfg["hemi_layers"]) | |
| dff_dense = int(struct_cfg["dff_dense"]) | |
| dff_expert = int(struct_cfg["dff_expert"]) | |
| topk = int(struct_cfg["topk"]) | |
| dense_layers = Ls + 2 * Lh | |
| moe_per_hemi = Lh // 2 | |
| dense_per_hemi = Lh - moe_per_hemi | |
| ffn_units = (Ls * dff_dense) + (2 * dense_per_hemi * dff_dense) + (2 * moe_per_hemi * topk * dff_expert) | |
| dense_dff = ffn_units / dense_layers | |
| dense_dff_round = max(round_multiple, int(round_multiple * round(dense_dff / round_multiple))) | |
| return dense_layers, dense_dff_round | |
| def collate(batch_ids: List[List[int]], pad_id: int, max_len: int) -> torch.Tensor: | |
| B = len(batch_ids) | |
| T = min(max(len(x) for x in batch_ids), max_len) | |
| out = torch.full((B, T), pad_id, dtype=torch.long) | |
| for i, ids in enumerate(batch_ids): | |
| ids = ids[:T] | |
| out[i, :len(ids)] = torch.tensor(ids, dtype=torch.long) | |
| return out | |
| def eval_split( | |
| model_s, model_d, | |
| device, use_amp: bool, amp_dtype, | |
| split_name: str, | |
| records: List[Dict], | |
| cfg: Dict, | |
| encode_obj: Callable[[Dict,int,int,int], Tuple[List[int], int]], | |
| ) -> Dict: | |
| vocab = int(cfg["task"]["vocab_size"]) | |
| max_len = int(cfg["train"]["seq_max_len"]) | |
| noise_vocab = int(cfg["task"]["noise_vocab"]) | |
| eval_bs = int(cfg["train"].get("eval_batch_size", 64)) | |
| y_gold, y_pred_s, y_pred_d = [], [], [] | |
| sum_loss_s, sum_loss_d = 0.0, 0.0 | |
| disag, incorrect = [], [] | |
| autocast_ctx = torch.autocast(device_type="cuda", dtype=amp_dtype, enabled=(use_amp and device.type=="cuda")) | |
| n_total = 0 | |
| for i in range(0, len(records), eval_bs): | |
| chunk = records[i:i+eval_bs] | |
| batch_ids, batch_tgt = [], [] | |
| for r in chunk: | |
| ids, tgt = encode_obj(r, vocab, noise_vocab, max_len) | |
| batch_ids.append(ids) | |
| batch_tgt.append(tgt) | |
| inp = collate(batch_ids, pad_id=0, max_len=max_len).to(device) | |
| tgt = torch.tensor(batch_tgt, device=device, dtype=torch.long) | |
| with autocast_ctx: | |
| logits_s, aux = model_s(inp, return_aux=True, global_step=10**9) | |
| logits_d = model_d(inp) | |
| loss_s = F.cross_entropy(logits_s.float(), tgt, reduction="sum") | |
| loss_d = F.cross_entropy(logits_d.float(), tgt, reduction="sum") | |
| ps = torch.argmax(logits_s, dim=-1).detach().cpu().numpy().astype(np.int64) | |
| pd = torch.argmax(logits_d, dim=-1).detach().cpu().numpy().astype(np.int64) | |
| y_gold.extend(batch_tgt) | |
| y_pred_s.extend(ps.tolist()) | |
| y_pred_d.extend(pd.tolist()) | |
| dk = aux["sym_kl"].float().detach().cpu().numpy() | |
| disag.extend(dk.tolist()) | |
| incorrect.extend((ps != np.array(batch_tgt, dtype=np.int64)).astype(np.int64).tolist()) | |
| bs = len(batch_tgt) | |
| n_total += bs | |
| sum_loss_s += float(loss_s.item()) | |
| sum_loss_d += float(loss_d.item()) | |
| y_gold_np = np.array(y_gold, dtype=np.int64) | |
| acc_s = accuracy(np.array(y_pred_s), y_gold_np) | |
| acc_d = accuracy(np.array(y_pred_d), y_gold_np) | |
| disag_np = np.array(disag, dtype=np.float64) | |
| incorrect_np = np.array(incorrect, dtype=np.int64) | |
| return { | |
| f"{split_name}/loss_s": float(sum_loss_s / max(1, n_total)), | |
| f"{split_name}/loss_d": float(sum_loss_d / max(1, n_total)), | |
| f"{split_name}/acc_s": acc_s, | |
| f"{split_name}/acc_d": acc_d, | |
| f"{split_name}/disagree_mean": float(disag_np.mean()), | |
| f"{split_name}/disagree_correct_mean": float(disag_np[incorrect_np==0].mean()) if (incorrect_np==0).any() else float("nan"), | |
| f"{split_name}/disagree_incorrect_mean": float(disag_np[incorrect_np==1].mean()) if (incorrect_np==1).any() else float("nan"), | |
| } | |
| def build_eval_sets(cfg: Dict, seed: int, make_record_fn, record_to_json_fn) -> Dict[str, List[Dict]]: | |
| rng = random.Random(seed) | |
| tcfg = cfg["task"] | |
| scfg = cfg["splits"] | |
| # IDs vary by task; the make_record_fn will validate usage | |
| key_ids = list(range(int(tcfg["key_start"]), int(tcfg["key_start"]) + int(tcfg["key_count"]))) | |
| val_ids = list(range(int(tcfg["val_start"]), int(tcfg["val_start"]) + int(tcfg["val_count"]))) | |
| mid_ids = list(range(int(tcfg.get("mid_start", 1024)), int(tcfg.get("mid_start", 1024)) + int(tcfg.get("mid_count", 128)))) | |
| def mk(n, nmin, nmax, fmt="train", gap_max=0): | |
| out = [] | |
| for _ in range(n): | |
| npairs = rng.randint(nmin, nmax) | |
| rec = make_record_fn(rng, npairs, key_ids, mid_ids, val_ids, fmt=fmt, gap_max=gap_max) \ | |
| if tcfg["name"] == "twohop_bind" \ | |
| else make_record_fn(rng, npairs, key_ids, val_ids, fmt=fmt, gap_max=gap_max) | |
| out.append(record_to_json_fn(rec)) | |
| return out | |
| n_iid = int(cfg["train"].get("eval_size_iid", 256)) | |
| n_ood = int(cfg["train"].get("eval_size_ood", 256)) | |
| n_pert = int(cfg["train"].get("eval_size_pert", 256)) | |
| n_long = int(cfg["train"].get("eval_size_long", n_ood)) | |
| n_braid = int(cfg["train"].get("eval_size_braid", n_pert)) | |
| return { | |
| "iid": mk(n_iid, int(scfg["train_pairs_min"]), int(scfg["train_pairs_max"]), fmt="train", gap_max=0), | |
| "ood": mk(n_ood, int(scfg["ood_pairs_min"]), int(scfg["ood_pairs_max"]), fmt="train", gap_max=0), | |
| "ood_long": mk( | |
| n_long, | |
| int(scfg.get("long_pairs_min", scfg["ood_pairs_min"])), | |
| int(scfg.get("long_pairs_max", scfg["ood_pairs_max"])), | |
| fmt="perturb_gap", | |
| gap_max=int(scfg.get("long_gap_max", max(12, int(scfg.get("pert_gap_max", 6))*2))), | |
| ), | |
| "braid": mk( | |
| n_braid, | |
| int(scfg["train_pairs_min"]), | |
| int(scfg["train_pairs_max"]), | |
| fmt="braid", | |
| gap_max=int(scfg.get("braid_gap_max", 0)), | |
| ), | |
| "pert": mk( | |
| n_pert, | |
| int(scfg["train_pairs_min"]), | |
| int(scfg["train_pairs_max"]), | |
| fmt="perturb_gap", | |
| gap_max=int(scfg.get("pert_gap_max", 6)), | |
| ), | |
| } | |
| def dump_english_examples(out_dir: str, eval_sets: Dict[str, List[Dict]], n: int, render_q, render_a, solve_from_json): | |
| os.makedirs(out_dir, exist_ok=True) | |
| for split, recs in eval_sets.items(): | |
| path = os.path.join(out_dir, f"examples_{split}.txt") | |
| with open(path, "w", encoding="utf-8") as f: | |
| for r in recs[:n]: | |
| f.write(render_q(r)) | |
| f.write("\n") | |
| gold = solve_from_json(r) | |
| f.write(render_a(gold)) | |
| f.write("\n" + ("-"*60) + "\n") | |
| def mem_snapshot(): | |
| if not torch.cuda.is_available(): | |
| return {} | |
| return { | |
| "cuda_alloc": int(torch.cuda.memory_allocated()), | |
| "cuda_reserved": int(torch.cuda.memory_reserved()), | |
| "cuda_peak": int(torch.cuda.max_memory_allocated()), | |
| } | |
| def main(): | |
| import argparse | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--config", required=True) | |
| args = ap.parse_args() | |
| cfg = yaml.safe_load(open(args.config, "r", encoding="utf-8")) | |
| seed = int(cfg["run"]["seed"]) | |
| set_seed(seed) | |
| device = torch.device("cuda" if torch.cuda.is_available() else "cpu") | |
| if device.type == "cuda": | |
| torch.backends.cuda.matmul.allow_tf32 = True | |
| use_amp, amp_dtype, scaler = amp_setup(device, cfg["model_common"]["dtype"]) | |
| vocab = int(cfg["task"]["vocab_size"]) | |
| run_id = now_run_id(str(cfg["run"]["tag"])) | |
| out_dir = os.path.join(str(cfg["run"]["out_root"]), run_id) | |
| os.makedirs(out_dir, exist_ok=True) | |
| with open(os.path.join(out_dir, "config.yaml"), "w", encoding="utf-8") as f: | |
| yaml.safe_dump(cfg, f, sort_keys=False) | |
| # --- Task selection --- | |
| task_name = str(cfg["task"]["name"]) | |
| if task_name == "bind_query": | |
| from bihmoe.tasks.bind_query import make_record as mk, record_to_json as r2j, encode as enc, BindQueryRecord, solve | |
| def encode_obj(r: Dict, vocab: int, noise_vocab: int, max_len: int): | |
| rec = BindQueryRecord(pairs=[tuple(p) for p in r["pairs"]], query_k=int(r["query_k"]), fmt=str(r.get("fmt","train")), gap_max=int(r.get("gap_max",0))) | |
| return enc(rec, vocab, noise_vocab=noise_vocab, max_len=max_len) | |
| def solve_from_json(r: Dict) -> int: | |
| rec = BindQueryRecord(pairs=[tuple(p) for p in r["pairs"]], query_k=int(r["query_k"]), fmt=str(r.get("fmt","train")), gap_max=int(r.get("gap_max",0))) | |
| return solve(rec) | |
| render_q, render_a = render_bind_query_q, render_bind_query_a | |
| elif task_name == "twohop_bind": | |
| from bihmoe.tasks.twohop_bind import make_record as mk2, record_to_json as r2j2, encode as enc2, TwoHopBindRecord, solve as solve2 | |
| def encode_obj(r: Dict, vocab: int, noise_vocab: int, max_len: int): | |
| rec = TwoHopBindRecord( | |
| f_pairs=[tuple(p) for p in r["f_pairs"]], | |
| g_pairs=[tuple(p) for p in r["g_pairs"]], | |
| query_k=int(r["query_k"]), | |
| fmt=str(r.get("fmt","train")), | |
| gap_max=int(r.get("gap_max",0)), | |
| ) | |
| return enc2(rec, vocab, noise_vocab=noise_vocab, max_len=max_len) | |
| def solve_from_json(r: Dict) -> int: | |
| rec = TwoHopBindRecord( | |
| f_pairs=[tuple(p) for p in r["f_pairs"]], | |
| g_pairs=[tuple(p) for p in r["g_pairs"]], | |
| query_k=int(r["query_k"]), | |
| fmt=str(r.get("fmt","train")), | |
| gap_max=int(r.get("gap_max",0)), | |
| ) | |
| return solve2(rec) | |
| mk, r2j, enc = mk2, r2j2, enc2 | |
| render_q, render_a = render_twohop_q, render_twohop_a | |
| else: | |
| raise SystemExit(f"Unknown task.name={task_name}") | |
| # Eval sets (fixed) | |
| eval_sets = build_eval_sets(cfg, seed=seed + 999, make_record_fn=mk, record_to_json_fn=r2j) | |
| with open(os.path.join(out_dir, "eval_sets.json"), "w", encoding="utf-8") as f: | |
| json.dump(eval_sets, f) | |
| dump_english_examples(out_dir, eval_sets, n=8, render_q=render_q, render_a=render_a, solve_from_json=solve_from_json) | |
| # Dense compute-match | |
| if bool(cfg["dense"].get("auto_from_struct", True)): | |
| dense_layers, dense_dff = compute_match_dense(cfg["structured"], int(cfg["dense"].get("round_multiple",256))) | |
| else: | |
| dense_layers = int(cfg["dense"]["layers"]) | |
| dense_dff = int(cfg["dense"]["dff"]) | |
| d_model = int(cfg["model_common"]["d_model"]) | |
| n_heads = int(cfg["model_common"]["n_heads"]) | |
| model_d = DenseModel( | |
| vocab_size=vocab, d_model=d_model, n_heads=n_heads, | |
| n_layers=dense_layers, d_ff=dense_dff, | |
| dropout=0.0, head_mode="cls", pool=str(cfg.get("dense", {}).get("pool", "last")), | |
| pad_id=0 | |
| ).to(device) | |
| model_s = StructuredBiHMoE( | |
| vocab_size=vocab, d_model=d_model, n_heads=n_heads, | |
| n_layers_stem=int(cfg["structured"]["stem_layers"]), | |
| n_layers_hemi=int(cfg["structured"]["hemi_layers"]), | |
| d_ff_dense=int(cfg["structured"]["dff_dense"]), | |
| d_ff_expert=int(cfg["structured"]["dff_expert"]), | |
| n_experts=int(cfg["structured"]["experts"]), | |
| top_k=int(cfg["structured"]["topk"]), | |
| workspace_tokens=int(cfg["structured"]["workspace"]), | |
| reconcile_every=int(cfg["structured"]["reconcile_every"]), | |
| dropout=0.0, | |
| pad_id=0, | |
| fuse="mean", | |
| moe_warmup_steps=int(cfg.get('structured', {}).get('moe_warmup_steps', 0)), | |
| left_local_window=int(cfg.get('structured', {}).get('left_local_window', 0)), | |
| right_noise_std=float(cfg.get('structured', {}).get('right_noise_std', 0.0)), | |
| callosum_competitive=bool(cfg.get('structured', {}).get('callosum_competitive', True)), | |
| callosum_tau=float(cfg.get('structured', {}).get('callosum_tau', 1.0)), | |
| chiasm_enabled=bool(cfg.get('structured', {}).get('chiasm_enabled', False)), | |
| noise_vocab=int(cfg.get('task', {}).get('noise_vocab', 16)), | |
| key_start=int(cfg.get('task', {}).get('key_start', -1)), | |
| key_count=int(cfg.get('task', {}).get('key_count', 0)), | |
| mid_start=int(cfg.get('task', {}).get('mid_start', -1)), | |
| mid_count=int(cfg.get('task', {}).get('mid_count', 0)), | |
| val_start=int(cfg.get('task', {}).get('val_start', -1)), | |
| val_count=int(cfg.get('task', {}).get('val_count', 0)), | |
| output_gate=bool(cfg.get('structured', {}).get('output_gate', True)), | |
| output_tau=float(cfg.get('structured', {}).get('output_tau', 1.0)), | |
| ).to(device) | |
| opt_d = torch.optim.AdamW(model_d.parameters(), lr=float(cfg["train"]["lr"])) | |
| opt_s = torch.optim.AdamW(model_s.parameters(), lr=float(cfg["train"]["lr"])) | |
| # ID pools | |
| tcfg = cfg["task"] | |
| key_ids = list(range(int(tcfg["key_start"]), int(tcfg["key_start"]) + int(tcfg["key_count"]))) | |
| val_ids = list(range(int(tcfg["val_start"]), int(tcfg["val_start"]) + int(tcfg["val_count"]))) | |
| mid_ids = list(range(int(tcfg.get("mid_start", 1024)), int(tcfg.get("mid_start", 1024)) + int(tcfg.get("mid_count", 128)))) | |
| scfg = cfg["splits"] | |
| train_min = int(scfg["train_pairs_min"]) | |
| mix_pert_prob = float(cfg['train'].get('mix_pert_prob', 0.0)) | |
| mix_long_prob = float(cfg['train'].get('mix_long_prob', 0.0)) | |
| train_max = int(scfg["train_pairs_max"]) | |
| batch = int(cfg["train"]["batch_size"]) | |
| max_len = int(cfg["train"]["seq_max_len"]) | |
| noise_vocab = int(cfg["task"]["noise_vocab"]) | |
| steps = int(cfg["train"]["steps"]) | |
| eval_every = int(cfg["train"]["eval_every"]) | |
| grad_clip = float(cfg["train"].get("grad_clip", 1.0)) | |
| lb_weight = float(cfg["train"].get("lb_weight", 0.0)) | |
| metrics_path = os.path.join(out_dir, "metrics.jsonl") | |
| console.print(f"run_id: {run_id}") | |
| console.print(f"out_dir: {out_dir}") | |
| console.print(f"task: {task_name}") | |
| console.print(f"device: {device} amp: {use_amp} amp_dtype: {amp_dtype}") | |
| console.print(f"dense_layers/dff: {dense_layers} {dense_dff}") | |
| autocast_ctx = torch.autocast(device_type="cuda", dtype=amp_dtype, enabled=(use_amp and device.type=="cuda")) | |
| def make_train_batch(step: int): | |
| rng = random.Random((seed * 1_000_000) + step) | |
| cur = cfg.get("train", {}).get("curriculum", None) | |
| # Base knobs (fallback if no curriculum) | |
| mix_long = float(cfg["train"].get("mix_long_prob", 0.0)) | |
| mix_pert = float(cfg["train"].get("mix_pert_prob", 0.0)) | |
| mix_braid = float(cfg["train"].get("mix_braid_prob", 0.0)) | |
| long_pairs_min = int(scfg.get("long_pairs_min", scfg["ood_pairs_min"])) | |
| long_pairs_max = int(scfg.get("long_pairs_max", scfg["ood_pairs_max"])) | |
| long_gap_max = int(scfg.get("long_gap_max", max(12, int(scfg.get("pert_gap_max", 6))*2))) | |
| pert_gap_max = int(scfg.get("pert_gap_max", 6)) | |
| braid_gap_max = int(scfg.get("braid_gap_max", 0)) | |
| def lerp(a, b, t): return a + (b - a) * t | |
| if isinstance(cur, dict): | |
| p1 = int(cur.get("phase1_steps", 2000)) | |
| ramp = int(cur.get("ramp_steps", 0)) | |
| # Phase 1 | |
| mix_long1 = float(cur.get("mix_long1", mix_long)) | |
| mix_pert1 = float(cur.get("mix_pert1", mix_pert)) | |
| mix_braid1 = float(cur.get("mix_braid1", mix_braid)) | |
| # Phase 2 | |
| mix_long2 = float(cur.get("mix_long2", mix_long)) | |
| mix_pert2 = float(cur.get("mix_pert2", mix_pert)) | |
| mix_braid2 = float(cur.get("mix_braid2", mix_braid)) | |
| long_pairs_min2 = int(cur.get("long_pairs_min2", long_pairs_min)) | |
| long_pairs_max2 = int(cur.get("long_pairs_max2", long_pairs_max)) | |
| long_gap_max2 = int(cur.get("long_gap_max2", long_gap_max)) | |
| pert_gap_max2 = int(cur.get("pert_gap_max2", pert_gap_max)) | |
| braid_gap_max2 = int(cur.get("braid_gap_max2", braid_gap_max)) | |
| if ramp <= 0: | |
| if step <= p1: | |
| mix_long, mix_pert, mix_braid = mix_long1, mix_pert1, mix_braid1 | |
| else: | |
| mix_long, mix_pert, mix_braid = mix_long2, mix_pert2, mix_braid2 | |
| long_pairs_min, long_pairs_max = long_pairs_min2, long_pairs_max2 | |
| long_gap_max, pert_gap_max, braid_gap_max = long_gap_max2, pert_gap_max2, braid_gap_max2 | |
| else: | |
| if step <= p1: | |
| t = 0.0 | |
| elif step >= p1 + ramp: | |
| t = 1.0 | |
| else: | |
| t = (step - p1) / float(ramp) | |
| mix_long = float(lerp(mix_long1, mix_long2, t)) | |
| mix_pert = float(lerp(mix_pert1, mix_pert2, t)) | |
| mix_braid = float(lerp(mix_braid1, mix_braid2, t)) | |
| long_pairs_min = int(round(lerp(long_pairs_min, long_pairs_min2, t))) | |
| long_pairs_max = int(round(lerp(long_pairs_max, long_pairs_max2, t))) | |
| long_gap_max = int(round(lerp(long_gap_max, long_gap_max2, t))) | |
| pert_gap_max = int(round(lerp(pert_gap_max, pert_gap_max2, t))) | |
| braid_gap_max = int(round(lerp(braid_gap_max, braid_gap_max2, t))) | |
| # Normalize so total hard prob <= 0.95 | |
| mix_long = max(0.0, mix_long) | |
| mix_pert = max(0.0, mix_pert) | |
| mix_braid = max(0.0, mix_braid) | |
| total = mix_long + mix_pert + mix_braid | |
| if total > 0.95: | |
| scale = 0.95 / total | |
| mix_long *= scale | |
| mix_pert *= scale | |
| mix_braid *= scale | |
| recs, tgts = [], [] | |
| for _ in range(batch): | |
| r = rng.random() | |
| fmt = "train" | |
| gap = 0 | |
| if r < mix_long: | |
| npairs = rng.randint(long_pairs_min, long_pairs_max) | |
| fmt = "perturb_gap" | |
| gap = long_gap_max | |
| elif r < (mix_long + mix_pert): | |
| npairs = rng.randint(train_min, train_max) | |
| fmt = "perturb_gap" | |
| gap = pert_gap_max | |
| elif r < (mix_long + mix_pert + mix_braid): | |
| npairs = rng.randint(train_min, train_max) | |
| fmt = "braid" | |
| gap = braid_gap_max | |
| else: | |
| npairs = rng.randint(train_min, train_max) | |
| fmt = "train" | |
| gap = 0 | |
| if task_name == "twohop_bind": | |
| rec = mk(rng, npairs, key_ids, mid_ids, val_ids, fmt=fmt, gap_max=gap) | |
| else: | |
| rec = mk(rng, npairs, key_ids, val_ids, fmt=fmt, gap_max=gap) | |
| ids, tgt = enc(rec, vocab, noise_vocab=noise_vocab, max_len=max_len) | |
| recs.append(ids) | |
| tgts.append(tgt) | |
| return recs, tgts | |
| for step in range(1, steps + 1): | |
| recs, tgts = make_train_batch(step) | |
| inp = collate(recs, pad_id=0, max_len=max_len).to(device) | |
| tgt = torch.tensor(tgts, device=device, dtype=torch.long) | |
| # Structured step | |
| opt_s.zero_grad(set_to_none=True) | |
| with autocast_ctx: | |
| logits_s, aux_s = model_s(inp, return_aux=True, global_step=step) | |
| loss_s_main = F.cross_entropy(logits_s.float(), tgt) | |
| loss_s = loss_s_main + (lb_weight * aux_s.get('lb_loss', 0.0)) | |
| if not torch.isfinite(loss_s): | |
| console.print(f"NON-FINITE loss_s at step {step} = {float(loss_s)}") | |
| break | |
| if scaler is not None and scaler.is_enabled(): | |
| scaler.scale(loss_s).backward() | |
| scaler.unscale_(opt_s) | |
| torch.nn.utils.clip_grad_norm_(model_s.parameters(), grad_clip) | |
| scaler.step(opt_s) | |
| else: | |
| loss_s.backward() | |
| torch.nn.utils.clip_grad_norm_(model_s.parameters(), grad_clip) | |
| opt_s.step() | |
| # Dense step | |
| opt_d.zero_grad(set_to_none=True) | |
| with autocast_ctx: | |
| logits_d = model_d(inp) | |
| loss_d = F.cross_entropy(logits_d.float(), tgt) | |
| if not torch.isfinite(loss_d): | |
| console.print(f"NON-FINITE loss_d at step {step} = {float(loss_d)}") | |
| break | |
| if scaler is not None and scaler.is_enabled(): | |
| scaler.scale(loss_d).backward() | |
| scaler.unscale_(opt_d) | |
| torch.nn.utils.clip_grad_norm_(model_d.parameters(), grad_clip) | |
| scaler.step(opt_d) | |
| scaler.update() | |
| else: | |
| loss_d.backward() | |
| torch.nn.utils.clip_grad_norm_(model_d.parameters(), grad_clip) | |
| opt_d.step() | |
| if step % eval_every == 0 or step == 1: | |
| model_s.eval() | |
| model_d.eval() | |
| if torch.cuda.is_available(): | |
| torch.cuda.reset_peak_memory_stats() | |
| lbv = aux_s.get("lb_loss", 0.0) | |
| if hasattr(lbv, "detach"): | |
| lbv = float(lbv.detach().float().item()) | |
| else: | |
| lbv = float(lbv) | |
| metrics = {"step": step, "loss_s_train": float(loss_s.item()), "loss_d_train": float(loss_d.item()), "lb_loss": lbv, "lb_weight": float(lb_weight)} | |
| for split in ["iid","ood","ood_long","braid","pert"]: | |
| metrics.update(eval_split(model_s, model_d, device, use_amp, amp_dtype, split, eval_sets[split], cfg, encode_obj)) | |
| if torch.cuda.is_available(): | |
| metrics.update({ | |
| "cuda_alloc": int(torch.cuda.memory_allocated()), | |
| "cuda_reserved": int(torch.cuda.memory_reserved()), | |
| "cuda_peak": int(torch.cuda.max_memory_allocated()), | |
| }) | |
| metrics["cuda_alloc_h"] = fmt_bytes(metrics.get("cuda_alloc",0)) | |
| metrics["cuda_reserved_h"] = fmt_bytes(metrics.get("cuda_reserved",0)) | |
| metrics["cuda_peak_h"] = fmt_bytes(metrics.get("cuda_peak",0)) | |
| with open(metrics_path, "a", encoding="utf-8") as f: | |
| f.write(json.dumps(metrics) + "\n") | |
| print_metrics_pretty(metrics) | |
| model_s.train() | |
| model_d.train() | |
| console.print(f"done: {out_dir}") | |
| if __name__ == "__main__": | |
| main() | |