File size: 3,213 Bytes
32c0c6c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
"""Shared loading / inference helpers for the released Lean 4 tactic model.

Everything is relative to the package root, so the package works as-is wherever
it is unpacked.  A "variant" is a directory under checkpoints/ holding
lit_model.pth (and usually model.safetensors).
"""
import json
import os
import sys

import torch

HERE = os.path.dirname(os.path.abspath(__file__))
ROOT = os.path.abspath(os.path.join(HERE, os.pardir))
sys.path.insert(0, HERE)

from leanoar.model import ModelConfig, SchemeC          # noqa: E402
from tokenizers import Tokenizer                        # noqa: E402

CFG_PATH = os.path.join(ROOT, 'config.json')
VARIANTS = ('checkpoints/e6', 'checkpoints/stage3-e3')


def load_config():
    return json.load(open(CFG_PATH))


def specials():
    s = json.load(open(os.path.join(ROOT, 'special_tokens_v1.json')))
    return {str(k): int(v) for k, v in s.items()} if isinstance(s, dict) \
        else {str(i): int(v) for i, v in enumerate(s)}


def whitelist():
    return [w['id'] for w in json.load(open(os.path.join(ROOT, 'first_token_whitelist.json')))]


def pick_device(device=None):
    if device:
        return device
    return 'cuda' if torch.cuda.is_available() else 'cpu'


def resolve_ckpt(ckpt='checkpoints/e6'):
    """Accept a variant dir ('checkpoints/e6') or a direct path to a .pth/.safetensors."""
    if not os.path.isabs(ckpt) and not ckpt.startswith('checkpoints'):
        ckpt = os.path.join(ROOT, ckpt)
    if os.path.isdir(ckpt):
        return ckpt, os.path.join(ckpt, 'lit_model.pth'), os.path.join(ckpt, 'model.safetensors')
    return os.path.dirname(ckpt), ckpt, ckpt.replace('.pth', '.safetensors')


def load_model(ckpt='checkpoints/e6', device=None):
    """Build SchemeC from config.json and load a packaged variant. Returns (net, cfg, device)."""
    cfg_d = load_config()
    cfg = ModelConfig(**{k: v for k, v in cfg_d['arch'].items()
                         if k in ModelConfig.__dataclass_fields__})
    device = pick_device(device)
    dirname, pth, st = resolve_ckpt(ckpt)
    if not (os.path.exists(pth) or os.path.exists(st)):
        raise SystemExit(f'no weights found for {ckpt!r} (looked for {pth} / {st})')
    net = SchemeC(cfg).to(device).eval()
    if os.path.exists(st):
        from safetensors.torch import load_file
        net.load_state_dict(load_file(st))
    else:
        net.load_state_dict(torch.load(pth, map_location=device, weights_only=False)['model'])
    return net, cfg_d, device


def load_tokenizer():
    return Tokenizer.from_file(os.path.join(ROOT, 'tokenizer_v1.json'))


def encode_state(tok, state, cfg=None, max_new_tokens=None):
    """[bos, <|FORWARD|>, <|state|>] + state tokens + [<|tactic|>]; keeps head+tail if too long."""
    cfg = cfg or load_config()
    sp = specials()
    ctx = cfg['context_length']
    mnt = max_new_tokens or cfg['prompt_template']['max_new_tokens']
    sids = tok.encode(state).ids
    budget = ctx - 4 - mnt                      # 4 fixed tokens surround the state
    if len(sids) > budget:
        head = sids[:int(budget * 0.6)]
        sids = head + sids[-(budget - len(head)):]
    return [1, sp['<|FORWARD|>'], sp['<|state|>']] + list(sids) + [sp['<|tactic|>']]