Voltline's picture
Release VimeML V4.2 epoch20 FP32 and Core ML INT8 (GPL-2.0)
c83c617 verified
Raw History Blame Contribute Delete
7.83 kB
"""V4-only initialization, task identity, normalized updates and stop policy."""
import copy
import math
from pathlib import Path
import torch
from vimeml.posttraining.data import TOKENIZER_SHA
from vimeml.posttraining.infer import SCORE_CONTRACT, READING_CONTRACT
from vimeml.posttraining.streams import microbatches
from vimeml.training.data import file_sha
from vimeml.training.model_v4 import GPTV4Config
from vimeml.training.train import amp_context
STAGE = "v4-c1"
TASK_CONTRACT = {
"format": "BOS_CTX_context_READ_reading_OUT_surface_EOS_v1",
"labels": "already_shifted_OUT_and_EOS_only_PAD_minus100",
"lm": "ordinary_unmarked_LM_all_valid_targets",
"normalization": "independent_task_total_supervised_tokens_per_update",
"score": SCORE_CONTRACT, "reading": READING_CONTRACT,
"context_length": 128, "tokenizer_sha256": TOKENIZER_SHA,
"marker_ids": {"<CTX>": 4, "<READ>": 5, "<OUT>": 6},
}
def validate_task_checkpoint(saved):
if (saved.get("format") != "vimeml_tiny_gpt_v4" or saved.get("architecture") != "tiny_gpt_v4"
or saved.get("model_config") != GPTV4Config().__dict__
or saved.get("posttraining", {}).get("stage") not in (STAGE, "v4.1")
or saved["posttraining"].get("task_contract") != TASK_CONTRACT):
raise ValueError("Explicit compatible V4-C1 checkpoint required")
expected = {"tokenizer_model_sha256": TOKENIZER_SHA, "vocab_size": 8192,
"special_ids": {"pad": 0, "unk": 1, "bos": 2, "eos": 3}}
if saved.get("tokenizer_identity") != expected:
raise ValueError("Conditional checkpoint tokenizer identity mismatch")
def initialize_v4(model, source, expected_sha, expected_step, tokenizer_identity, token_signature, window_signature):
"""Cross-stage initialization loads weights only, never AdamW/RNG/cursors."""
if file_sha(source) != expected_sha:
raise ValueError("Source checkpoint SHA256 mismatch")
saved = torch.load(source, map_location="cpu", weights_only=True)
if (saved.get("format") != "vimeml_tiny_gpt_v4" or saved.get("architecture") != "tiny_gpt_v4"
or saved.get("posttraining") or saved.get("model_config") != model.configuration()
or saved.get("step") != expected_step or saved.get("tokenizer_identity") != tokenizer_identity
or saved.get("signatures", {}).get("tokens") != token_signature
or saved.get("signatures", {}).get("windows") != window_signature):
raise ValueError("Source must be the frozen matching V4-B LM checkpoint")
model.load_state_dict(saved["model"], strict=True)
return {"checkpoint": str(Path(source)), "checkpoint_sha256": expected_sha,
"source_step": saved["step"], "source_total_tokens": saved["total_tokens"],
"source_epoch": saved["epoch"], "optimizer": "fresh_AdamW_no_source_state_restored"}
def update(model, optimizer, conversion, lm, settings, device, precision):
"""One atomic logical update; never average unequal microbatch means."""
denominators = {name: sum(int((b["labels"] != -100).sum()) for b in batches)
for name, batches in (("conversion", conversion), ("lm", lm))}
if min(denominators.values()) <= 0:
raise ValueError("Each task needs supervised targets")
optimizer.zero_grad(set_to_none=True)
losses, inputs, examples, positions = {}, {}, {}, {}
for name, batches in (("conversion", conversion), ("lm", lm)):
pieces = []
inputs[name] = sum(int(b["lengths"].sum()) for b in batches)
examples[name] = sum(len(b["lengths"]) for b in batches)
positions[name] = 0
for batch in microbatches(batches, settings[f"{name}_microbatch"]):
ids = batch["input_ids"].to(device, non_blocking=True)
labels = batch["labels"].to(device, non_blocking=True)
with amp_context(device, precision):
result = model(ids, labels)
loss = result["loss_sum"] * (settings[f"{name}_weight"] / denominators[name])
pieces.append(result["loss_sum"].detach())
# Non-finite gradients are rejected BEFORE optimizer.step by clip.
loss.backward()
positions[name] += ids.numel()
value = float(torch.stack(pieces).sum())
if not math.isfinite(value):
raise FloatingPointError(f"Non-finite {name} training loss")
losses[name] = value / denominators[name]
grad_norm = float(torch.nn.utils.clip_grad_norm_(model.parameters(), settings["grad_clip"], error_if_nonfinite=True))
optimizer.step()
return {"losses": losses, "supervised_tokens": denominators, "input_tokens": inputs,
"examples": examples, "padded_positions": positions, "grad_norm": grad_norm,
"weighted_loss": sum(losses[n] * settings[f"{n}_weight"] for n in losses)}
def initial_state():
return {"step": 0, "epoch": 0, "conversion_cursor": 0,
"lm_cursor": {"epoch": 0, "batch": 0, "offset": 0},
"epoch_results": [], "conversion_reference": None, "plateau_count": 0,
"forgetting_count": 0, "best_conversion": None, "best_lm": None,
"best_rerank_key": None, "stop_reason": None,
"totals": {name: {"examples": 0, "input_tokens": 0, "supervised_tokens": 0, "padded_positions": 0}
for name in ("conversion", "lm")}, "training_seconds": 0.0}
def apply_epoch_result(state, conversion_loss, lm_bpc, ime_correct, settings):
"""Returns checkpoint roles; caller applies exactly once per completed epoch."""
if not all(math.isfinite(x) and x > 0 for x in (conversion_loss, lm_bpc)):
raise ValueError("Invalid full validation metrics")
roles = []
if state["best_conversion"] is None or conversion_loss < state["best_conversion"]:
state["best_conversion"] = conversion_loss
roles.append("best-conversion")
if state["best_lm"] is None or lm_bpc < state["best_lm"]:
state["best_lm"] = lm_bpc
roles.append("best-lm")
reference = state["conversion_reference"]
if reference is None or conversion_loss < reference * (1 - settings["early_stopping_relative_delta"]):
state["conversion_reference"], state["plateau_count"] = conversion_loss, 0
else:
state["plateau_count"] += 1
allowed = lm_bpc <= settings["baseline_lm_bpc"] * (1 + settings["lm_relative_degradation"])
state["forgetting_count"] = 0 if allowed else state["forgetting_count"] + 1
key = [int(ime_correct), -conversion_loss, -state["step"]]
enforce_lm = settings.get("lm_retention_policy", "enforce") == "enforce"
if (allowed or not enforce_lm) and (state["best_rerank_key"] is None or key > state["best_rerank_key"]):
state["best_rerank_key"] = key
roles += ["best-rerank", "best"]
if enforce_lm and state["forgetting_count"] >= settings["lm_degradation_patience"]:
state["stop_reason"] = "lm_retention_guard"
elif state["plateau_count"] >= settings["early_stopping_patience"]:
state["stop_reason"] = "conversion_plateau"
return roles, allowed
def restore_checkpoint(saved, model, optimizer, config, signatures, precision):
validate_task_checkpoint(saved)
if saved["config"] != config or saved["signatures"] != signatures or saved["precision"] != precision:
raise ValueError("C1 resume requires identical config, code, data, task and precision")
if saved["step"] != saved["training_state"]["step"] or saved["epoch"] != saved["training_state"]["epoch"]:
raise ValueError("Checkpoint header and committed state disagree")
model.load_state_dict(saved["model"], strict=True)
optimizer.load_state_dict(saved["optimizer"])
return copy.deepcopy(saved["training_state"]), saved["rng"], saved["initialization"]