Compactbot commited on
Commit
ced35f2
·
verified ·
1 Parent(s): 55c2795

Add standalone inference (torch + tokenizers + safetensors)

Browse files
Files changed (1) hide show
  1. generate.py +192 -0
generate.py ADDED
@@ -0,0 +1,192 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """catgirl-1m standalone inference. Needs only: torch, tokenizers, safetensors.
2
+
3
+ A full fine-tune of Glint-Research/Glint-2 (1,064,737 params) into a
4
+ tsundere-catgirl persona.
5
+
6
+ Usage:
7
+ python generate.py "My name is"
8
+ python generate.py --max-new-tokens 120 --temperature 0.7 "User: hey there"
9
+
10
+ The base is one shared transformer block looped 8 times, with a tiny per-loop
11
+ LoRA and a loop-embedding that tells it which iteration it is on. The
12
+ checkpoint's loop tables have capacity 16; run it at 8 loops (the value it was
13
+ trained at). More loops produce gibberish.
14
+ """
15
+ import argparse
16
+ import json
17
+ import math
18
+ import os
19
+
20
+ import torch
21
+ from tokenizers import Tokenizer
22
+ from torch import nn
23
+ from torch.nn import functional
24
+ from safetensors.torch import load_file
25
+
26
+ LOOPS = 8
27
+ ATTENTION_WINDOW = 256
28
+
29
+
30
+ class SwiGlu(nn.Module):
31
+ def __init__(self, dim, hidden):
32
+ super().__init__()
33
+ self.gate_up = nn.Linear(dim, 2 * hidden, bias=False)
34
+ self.down = nn.Linear(hidden, dim, bias=False)
35
+
36
+ def forward(self, x):
37
+ gate, up = self.gate_up(x).chunk(2, dim=-1)
38
+ return self.down(functional.silu(gate) * up)
39
+
40
+
41
+ class Attention(nn.Module):
42
+ def __init__(self, dim, n_heads):
43
+ super().__init__()
44
+ self.n_heads = n_heads
45
+ self.head_dim = dim // n_heads
46
+ self.qkv = nn.Linear(dim, 3 * dim, bias=False)
47
+ self.out = nn.Linear(dim, dim, bias=False)
48
+
49
+ def forward(self, x, cos, sin, qkv_delta):
50
+ batch, seq_len, dim = x.shape
51
+ qkv = self.qkv(x) + qkv_delta
52
+ q, k, v = qkv.split(dim, dim=-1)
53
+ q = q.view(batch, seq_len, self.n_heads, self.head_dim).transpose(1, 2)
54
+ k = k.view(batch, seq_len, self.n_heads, self.head_dim).transpose(1, 2)
55
+ v = v.view(batch, seq_len, self.n_heads, self.head_dim).transpose(1, 2)
56
+ q, k = apply_rope(q, cos, sin), apply_rope(k, cos, sin)
57
+ pos = torch.arange(seq_len, device=x.device)
58
+ mask = (pos[None, :] <= pos[:, None]) & (pos[:, None] - pos[None, :] < ATTENTION_WINDOW)
59
+ attended = functional.scaled_dot_product_attention(
60
+ q, k, v, attn_mask=mask, scale=1.0 / math.sqrt(self.head_dim)
61
+ )
62
+ return self.out(attended.transpose(1, 2).reshape(batch, seq_len, dim))
63
+
64
+
65
+ def apply_rope(x, cos, sin):
66
+ x_even, x_odd = x[..., 0::2], x[..., 1::2]
67
+ return torch.stack(
68
+ (x_even * cos - x_odd * sin, x_even * sin + x_odd * cos), dim=-1
69
+ ).flatten(-2)
70
+
71
+
72
+ class Block(nn.Module):
73
+ def __init__(self, dim, n_heads, ffn_hidden):
74
+ super().__init__()
75
+ self.attn_norm = nn.RMSNorm(dim)
76
+ self.attn = Attention(dim, n_heads)
77
+ self.ffn_norm = nn.RMSNorm(dim)
78
+ self.ffn = SwiGlu(dim, ffn_hidden)
79
+
80
+ def forward(self, x, cos, sin, qkv_delta):
81
+ x = x + self.attn(self.attn_norm(x), cos, sin, qkv_delta)
82
+ return x + self.ffn(self.ffn_norm(x))
83
+
84
+
85
+ class LoopLora(nn.Module):
86
+ def __init__(self, dim, rank, max_loops):
87
+ super().__init__()
88
+ self.down = nn.ModuleList(nn.Linear(dim, rank, bias=False) for _ in range(max_loops))
89
+ self.up = nn.ModuleList(nn.Linear(rank, 3 * dim, bias=False) for _ in range(max_loops))
90
+
91
+ def forward(self, x, loop_index):
92
+ i = min(loop_index, len(self.down) - 1)
93
+ return self.up[i](self.down[i](x))
94
+
95
+
96
+ class Indexer(nn.Module):
97
+ def __init__(self):
98
+ super().__init__()
99
+ self.gate = nn.Parameter(torch.tensor([0.1]))
100
+
101
+
102
+ class Glint2(nn.Module):
103
+ def __init__(self, cfg, max_loops):
104
+ super().__init__()
105
+ self.cfg = cfg
106
+ self.max_loops = max_loops
107
+ self.embed = nn.Embedding(cfg["vocab_size"], cfg["dim"])
108
+ self.indexer = Indexer()
109
+ self.shared = Block(cfg["dim"], cfg["n_heads"], cfg["ffn_hidden"])
110
+ self.loop_lora = LoopLora(cfg["dim"], cfg["lora_rank"], max_loops)
111
+ self.loop_embed = nn.Embedding(max_loops, cfg["dim"])
112
+ self.final_norm = nn.RMSNorm(cfg["dim"])
113
+
114
+ def rope(self, seq_len):
115
+ head_dim = self.cfg["dim"] // self.cfg["n_heads"]
116
+ positions = torch.arange(seq_len, dtype=torch.float32)
117
+ inv_freq = 1.0 / (
118
+ self.cfg["rope_base"]
119
+ ** (torch.arange(0, head_dim, 2, dtype=torch.float32) / head_dim)
120
+ )
121
+ angles = torch.outer(positions, inv_freq)
122
+ return torch.cos(angles), torch.sin(angles)
123
+
124
+ def forward(self, tokens, loops=LOOPS, return_hidden=False):
125
+ cos, sin = self.rope(tokens.shape[1])
126
+ x = self.embed(tokens)
127
+ for loop_index in range(loops):
128
+ clamped = min(loop_index, self.max_loops - 1)
129
+ gated = x + self.loop_embed.weight[clamped]
130
+ delta = self.loop_lora(gated, loop_index)
131
+ x = self.shared(gated, cos, sin, delta)
132
+ x = self.final_norm(x)
133
+ if return_hidden:
134
+ return x
135
+ return functional.linear(x, self.embed.weight)
136
+
137
+
138
+ def load_model(model_dir):
139
+ with open(os.path.join(model_dir, "config.json")) as f:
140
+ cfg = json.load(f)
141
+ state = load_file(os.path.join(model_dir, "catgirl-1m.safetensors"))
142
+ model = Glint2(cfg, max_loops=cfg["max_loops"])
143
+ model.load_state_dict(state)
144
+ model.eval()
145
+ return model
146
+
147
+
148
+ def sample(logits, ids, temperature, top_k, repetition_penalty, repetition_window):
149
+ if repetition_penalty != 1.0:
150
+ for token in set(ids[-repetition_window:]):
151
+ if logits[token] > 0:
152
+ logits[token] /= repetition_penalty
153
+ else:
154
+ logits[token] *= repetition_penalty
155
+ if temperature <= 0:
156
+ return int(logits.argmax())
157
+ values, indices = logits.topk(top_k)
158
+ probs = functional.softmax(values / temperature, dim=-1)
159
+ return int(indices[torch.multinomial(probs, 1)])
160
+
161
+
162
+ def main():
163
+ parser = argparse.ArgumentParser(description="catgirl-1m text generation")
164
+ parser.add_argument("prompt")
165
+ parser.add_argument("--model-dir", default=os.path.dirname(os.path.abspath(__file__)))
166
+ parser.add_argument("--max-new-tokens", type=int, default=100)
167
+ parser.add_argument("--temperature", type=float, default=0.15, help="0 = greedy")
168
+ parser.add_argument("--top-k", type=int, default=5)
169
+ parser.add_argument("--repetition-penalty", type=float, default=1.05)
170
+ parser.add_argument("--repetition-window", type=int, default=128)
171
+ parser.add_argument("--loops", type=int, default=LOOPS)
172
+ args = parser.parse_args()
173
+
174
+ tokenizer = Tokenizer.from_file(os.path.join(args.model_dir, "tokenizer.json"))
175
+ model = load_model(args.model_dir)
176
+ ids = tokenizer.encode(args.prompt).ids
177
+ print(args.prompt, end="", flush=True)
178
+ with torch.no_grad():
179
+ for _ in range(args.max_new_tokens):
180
+ logits = model(torch.tensor([ids]), loops=args.loops)[0, -1]
181
+ token = sample(
182
+ logits, ids, args.temperature, args.top_k,
183
+ args.repetition_penalty, args.repetition_window,
184
+ )
185
+ new_text = tokenizer.decode(ids + [token])[len(tokenizer.decode(ids)):]
186
+ ids.append(token)
187
+ print(new_text, end="", flush=True)
188
+ print()
189
+
190
+
191
+ if __name__ == "__main__":
192
+ main()