Voltline's picture
Release VimeML V4.2 epoch20 FP32 and Core ML INT8 (GPL-2.0)
c83c617 verified
Raw History Blame Contribute Delete
4.63 kB
"""V4 checkpoint inference and cached joint-tokenized candidate scoring."""
from pathlib import Path
import sentencepiece as spm
import torch
from vimeml.training.data import file_sha
from vimeml.training.infer import JapaneseLM, ROOT
from vimeml.training.model_factory_v4 import FORMAT
from vimeml.training.model_factory import model_from_checkpoint
from vimeml.training.evaluate_ime import common_prefix_length
from vimeml.tokenizer.v4 import read_json
class JapaneseLMV4(JapaneseLM):
def __init__(self, checkpoint, tokenizer_dir, device="cpu"):
self.device = torch.device(device)
saved = torch.load(checkpoint, map_location="cpu", weights_only=True)
if saved.get("format") != FORMAT or saved.get("architecture") != "tiny_gpt_v4":
raise ValueError("Explicit V4 checkpoint required")
token_manifest = ROOT / saved["config"]["token_dir"] / "manifest.json"
if file_sha(token_manifest) != saved["signatures"]["tokens"]:
raise ValueError("Token data identity changed")
tokens = read_json(token_manifest)
model_path = Path(tokenizer_dir) / "tokenizer.model"
digest = file_sha(model_path)
if digest != tokens["tokenizer_model_sha256"]:
raise ValueError("V4 tokenizer identity changed")
self.processor = spm.SentencePieceProcessor(model_file=str(model_path))
self.special = {name: getattr(self.processor, f"{name}_id")() for name in ("pad", "unk", "bos", "eos")}
identity = {"tokenizer_model_sha256": digest, "vocab_size": 8192, "special_ids": self.special}
if (self.special != tokens["special_ids"] or self.processor.vocab_size() != 8192
or saved.get("tokenizer_identity") != identity):
raise ValueError("Checkpoint tokenizer contract mismatch")
self.model = model_from_checkpoint(saved).to(self.device).eval()
self.forbidden = (0, 1, 2, 4, 5, 6)
self.metadata = {"architecture": "tiny_gpt_v4", "checkpoint_sha256": file_sha(Path(checkpoint)),
"checkpoint_step": saved["step"], "tokenizer_sha256": digest,
"model": self.model.configuration(), "precision": "fp32",
"random_initialization_test_only": saved.get("random_initialization_test_only", False)}
def prefix_ids(self, prompt):
ids = super().prefix_ids(prompt)
if any(i in (4, 5, 6) for i in ids):
raise ValueError("Reserved conversion markers are not ordinary V4-a text")
return ids
@torch.inference_mode()
def score_candidates_cached(lm, context, candidates):
"""Identical full-vocabulary suffix logP sum; no final EOS/truncation."""
if not candidates or len(set(candidates)) != len(candidates) or any(not c for c in candidates):
raise ValueError("Distinct nonempty candidates required")
context_ids = lm.prefix_ids(context)
sequences = [[2, *lm.processor.encode(context + c)] for c in candidates]
for text, sequence in zip(candidates, sequences, strict=True):
if (lm.processor.decode(sequence[1:]) != context + text or any(i in range(7) for i in sequence[1:])
or not 1 <= len(sequence) - 1 <= 128):
raise ValueError("Invalid candidate or context overflow")
common = common_prefix_length([context_ids, *sequences])
if common < 1 or any(len(seq) <= common for seq in sequences):
raise ValueError("Candidate suffix must contain prediction targets")
shared = lm.model.new_cache()
initial = lm.model.forward_cached(torch.tensor([sequences[0][:common]], device=lm.device), shared)[0, -1]
results = []
for candidate, sequence in zip(candidates, sequences, strict=True):
cache, logits = shared.clone(), initial
values = []
suffix = sequence[common:]
for index, token in enumerate(suffix):
values.append(float(logits.float().log_softmax(-1)[token]))
if index + 1 < len(suffix):
logits = lm.model.forward_cached(torch.tensor([[token]], device=lm.device), cache)[0, -1]
results.append({"text": candidate, "log_probability_sum": sum(values),
"log_probability_mean": sum(values) / len(values), "scored_tokens": len(values),
"token_ids": suffix, "token_log_probabilities": values,
"pieces": [lm.processor.id_to_piece(i) for i in suffix],
"boundary_retokenized": sequence[:len(context_ids)] != context_ids})
return {"common_prefix_tokens_including_bos": common,
"context_tokens_including_bos": len(context_ids), "candidates": results}