File size: 3,946 Bytes
9204f7b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
"""Load ANKA-50M RMW3 and generate text with the released checkpoint."""

from __future__ import annotations

import argparse
import json
from pathlib import Path

import torch
from safetensors.torch import load_file
from tokenizers import Tokenizer

from modeling_anka import RMW3, RMW3Config, count_parameters


def load_model(model_dir: str | Path, device: str | None = None):
    model_dir = Path(model_dir)
    device = device or ("cuda" if torch.cuda.is_available() else "cpu")
    config_data = json.loads((model_dir / "config.json").read_text())
    config = RMW3Config(
        **{
            field: config_data[field]
            for field in RMW3Config.__dataclass_fields__
        }
    )

    model = RMW3(config)
    dtype = torch.bfloat16 if device.startswith("cuda") else torch.float32
    model = model.to(device=device, dtype=dtype)
    state_dict = load_file(str(model_dir / "model.safetensors"), device="cpu")
    model.load_state_dict(state_dict, strict=True)
    model.eval()

    if count_parameters(model) != 48_944_657:
        raise RuntimeError("Unexpected parameter count")
    if model.lm_head.weight.data_ptr() != model.token_embeddings.weight.data_ptr():
        raise RuntimeError("Embedding/output weight tying is not active")

    tokenizer = Tokenizer.from_file(str(model_dir / "tokenizer.json"))
    return model, tokenizer


@torch.inference_mode()
def generate(
    model: RMW3,
    tokenizer: Tokenizer,
    prompt: str,
    max_new_tokens: int = 128,
    temperature: float = 0.8,
    top_p: float = 0.95,
    seed: int = 3407,
) -> str:
    device = next(model.parameters()).device
    token_ids = tokenizer.encode(prompt, add_special_tokens=False).ids or [1]
    generated: list[int] = []
    generator = torch.Generator(device=device).manual_seed(seed)

    for _ in range(max_new_tokens):
        active = (token_ids + generated)[-model.config.max_seq_len :]
        input_ids = torch.tensor([active], device=device, dtype=torch.long)
        with torch.autocast(
            device_type=device.type,
            dtype=torch.bfloat16,
            enabled=device.type == "cuda",
        ):
            logits, _ = model(input_ids)
        next_logits = logits[0, -1].float()

        if temperature <= 0:
            next_id = int(next_logits.argmax())
        else:
            probabilities = torch.softmax(next_logits / temperature, dim=-1)
            if top_p < 1.0:
                sorted_probs, sorted_ids = torch.sort(probabilities, descending=True)
                remove = (torch.cumsum(sorted_probs, dim=-1) - sorted_probs) > top_p
                sorted_probs[remove] = 0
                sorted_probs /= sorted_probs.sum().clamp_min(1e-12)
                sampled = torch.multinomial(sorted_probs, 1, generator=generator)
                next_id = int(sorted_ids[sampled].item())
            else:
                next_id = int(torch.multinomial(probabilities, 1, generator=generator))

        if next_id == 2:
            break
        generated.append(next_id)

    completion = tokenizer.decode(generated, skip_special_tokens=True)
    return prompt + completion


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--model-dir", type=Path, default=Path(__file__).parent)
    parser.add_argument("--prompt", default="The future of artificial intelligence")
    parser.add_argument("--max-new-tokens", type=int, default=128)
    parser.add_argument("--temperature", type=float, default=0.8)
    parser.add_argument("--top-p", type=float, default=0.95)
    parser.add_argument("--seed", type=int, default=3407)
    args = parser.parse_args()

    model, tokenizer = load_model(args.model_dir)
    print(
        generate(
            model,
            tokenizer,
            args.prompt,
            args.max_new_tokens,
            args.temperature,
            args.top_p,
            args.seed,
        )
    )


if __name__ == "__main__":
    main()