adyoi commited on
Commit
b816de0
·
verified ·
1 Parent(s): cee0471

Upload chat.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. chat.py +211 -0
chat.py ADDED
@@ -0,0 +1,211 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ import json
3
+ import os
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ import numpy as np
8
+
9
+ ROOT = Path(__file__).resolve().parent
10
+ INDIGO_TORCH = ROOT.parent / "Indigo"
11
+ INDIGO_TF = ROOT.parent / "indigo.tf"
12
+
13
+
14
+ def meta_of(ckpt):
15
+ meta_path = str(ckpt)[: -len(".safetensors")] + "_meta.json"
16
+ if not os.path.exists(meta_path):
17
+ raise SystemExit(f"meta tidak ditemukan: {meta_path}")
18
+ with open(meta_path, encoding="utf-8") as f:
19
+ return json.load(f)
20
+
21
+
22
+ def load_torch(ckpt, meta, device):
23
+ sys.path.insert(0, str(INDIGO_TORCH))
24
+ from safetensors.torch import load_file
25
+
26
+ from indigo.common import build_tokenizer
27
+ from indigo.model import GPT, GPTConfig
28
+
29
+ state = load_file(str(ckpt))
30
+ model = GPT(GPTConfig(**meta["config"]))
31
+ missing, unexpected = model.load_state_dict(state, strict=False)
32
+ if missing or unexpected:
33
+ print(f"state_dict: missing={missing} unexpected={unexpected}")
34
+ model = model.to(device)
35
+ tokenizer = build_tokenizer(meta.get("tokenizer") or {"type": "char"}, meta.get("vocab"))
36
+ return model, tokenizer, "torch"
37
+
38
+
39
+ def load_tf(ckpt, meta):
40
+ sys.path.insert(0, str(INDIGO_TF))
41
+ import numpy as np
42
+ import tensorflow as tf
43
+ from safetensors.numpy import load_file
44
+
45
+ from indigotf.common import build_tokenizer
46
+ from indigotf.model import build_gpt, generate as tf_generate
47
+
48
+ keys = ("vocab_size", "block_size", "n_layer", "n_head", "n_embd", "dropout")
49
+ model = build_gpt(**{k: meta["config"][k] for k in keys})
50
+ state = {k.replace("/", "_"): v for k, v in load_file(str(ckpt)).items()}
51
+ by_path = {v.path.replace("/", "_"): v.path for v in model.weights}
52
+ missing = [p for p in by_path if p not in state]
53
+ if missing:
54
+ raise SystemExit(f"bobot tidak cocok: {missing[:5]}")
55
+ model.set_weights([state[v.path.replace("/", "_")] for v in model.weights])
56
+ tokenizer = build_tokenizer(meta.get("tokenizer") or {"type": "char"}, meta.get("vocab"))
57
+ n_params = int(sum(int(np.prod(v.shape)) for v in model.weights))
58
+ print(f"(tensorflow dimuat, params={n_params / 1e6:.2f}M)")
59
+ return model, tokenizer, "tf", tf_generate
60
+
61
+
62
+ def mean_nll(logp_fn, context, new_ids, block_size):
63
+ if not new_ids:
64
+ return float("inf")
65
+ seq = (context + list(new_ids))[-block_size:]
66
+ T = len(seq)
67
+ w = min(len(new_ids), max(T - 1, 1))
68
+ targets = np.asarray(seq[-w:])
69
+ logp = logp_fn(seq)
70
+ rows = np.arange(T - 1 - w, T - 1)
71
+ return float(-logp[rows, targets].mean())
72
+
73
+
74
+ def main():
75
+ global INDIGO_TORCH, INDIGO_TF
76
+ sys.stdout.reconfigure(encoding="utf-8", errors="replace")
77
+ parser = argparse.ArgumentParser(description="Chat tester untuk model Indigo (PyTorch & TensorFlow)")
78
+ parser.add_argument("--ckpt", default=None, help="path .safetensors (deteksi backend otomatis)")
79
+ parser.add_argument("--torch-dir", default=str(INDIGO_TORCH))
80
+ parser.add_argument("--tf-dir", default=str(INDIGO_TF))
81
+ parser.add_argument("--max-new", type=int, default=120)
82
+ parser.add_argument("--temperature", type=float, default=0.8)
83
+ parser.add_argument("--top-k", type=int, default=40)
84
+ parser.add_argument("--top-p", type=float, default=0.95)
85
+ parser.add_argument("--repetition-penalty", type=float, default=1.15)
86
+ parser.add_argument("--device", default="auto", choices=["auto", "cpu", "cuda"])
87
+ parser.add_argument("--fallback", default="saya tidak punya data.",
88
+ help='jawaban saat model tidak yakin; "" untuk mematikan')
89
+ parser.add_argument("--threshold", type=float, default=None,
90
+ help="ambang NLL prompt per token (default: val_loss - 0.6)")
91
+ parser.add_argument("--guard", default=None, help="kamus kata (satu/baris); fallback jika ratio rendah")
92
+ parser.add_argument("--guard-min", type=float, default=0.5, help="ambang rasio kata dikenal")
93
+ parser.add_argument("--show-nll", action="store_true", help="tampilkan skor NLL tiap jawaban")
94
+ args = parser.parse_args()
95
+
96
+ INDIGO_TORCH = Path(args.torch_dir)
97
+ INDIGO_TF = Path(args.tf_dir)
98
+
99
+ ckpt = args.ckpt
100
+ if ckpt is None:
101
+ cand = INDIGO_TORCH / "out" / "indigo_best.safetensors"
102
+ if cand.exists():
103
+ ckpt = cand
104
+ else:
105
+ raise SystemExit("tidak ada checkpoint; gunakan --ckpt")
106
+ ckpt = Path(ckpt)
107
+ meta = meta_of(ckpt)
108
+ backend = meta.get("backend", "pytorch")
109
+
110
+ wordset = prefiks = sufiks = None
111
+ if args.guard:
112
+ sys.path.insert(0, str(INDIGO_TORCH))
113
+ from indigo.common import load_wordlist as _load
114
+
115
+ wordset = _load(args.guard)
116
+ p_def = INDIGO_TORCH / "data" / "prefiks.txt"
117
+ s_def = INDIGO_TORCH / "data" / "sufiks.txt"
118
+ prefiks = _load(str(p_def)) if p_def.exists() else None
119
+ sufiks = _load(str(s_def)) if s_def.exists() else None
120
+
121
+ if backend == "tensorflow":
122
+ model, tokenizer, backend_label, tf_generate_fn = load_tf(ckpt, meta)
123
+ import tensorflow as tf
124
+
125
+ def idx_input(ctx):
126
+ return tf.constant([ctx], dtype=tf.int64)
127
+
128
+ def logp_fn(window):
129
+ logits = model(tf.constant([window], dtype=tf.int64), training=False)
130
+ return tf.nn.log_softmax(tf.cast(logits[0], tf.float32), axis=-1).numpy()
131
+
132
+ def generate_fn(mdl, idx, max_new, block_size_unused, temperature=1.0, top_k=None):
133
+ return tf_generate_fn(mdl, idx, max_new, meta["config"]["block_size"],
134
+ temperature=temperature, top_k=top_k)
135
+ else:
136
+ import torch
137
+
138
+ device = (
139
+ ("cuda" if torch.cuda.is_available() else "cpu") if args.device == "auto" else args.device
140
+ )
141
+ model, tokenizer, backend_label = load_torch(ckpt, meta, device)
142
+ dev = next(model.parameters()).device
143
+
144
+ def idx_input(ctx):
145
+ return torch.tensor([ctx], dtype=torch.long, device=dev)
146
+
147
+ def logp_fn(window):
148
+ idx = torch.tensor([window], dtype=torch.long, device=dev)
149
+ with torch.no_grad():
150
+ logits, _ = model(idx)
151
+ return torch.log_softmax(logits[0].float(), dim=-1).cpu().numpy()
152
+
153
+ def generate_fn(mdl, idx, max_new, block_size_unused, temperature=1.0, top_k=None):
154
+ return mdl.generate(idx, max_new, temperature=temperature, top_k=top_k,
155
+ top_p=args.top_p, repetition_penalty=args.repetition_penalty)
156
+
157
+ if args.threshold is None:
158
+ val = meta.get("val_loss")
159
+ args.threshold = max(3.0, val - 0.6) if val else 5.0
160
+ info_lines = [
161
+ f"model={ckpt.name} | backend={backend_label} | "
162
+ f"tokenizer={meta.get('tokenizer', {}).get('type', 'char')} | ambang nll={args.threshold:.2f}",
163
+ "perintah: /reset ulang konteks | /keluar berhenti",
164
+ ]
165
+ if wordset:
166
+ info_lines.append(f"[guard] kamus: {len(wordset):,} kata | ambang ratio >= {args.guard_min:.0%}")
167
+ print("\n".join(info_lines) + "\n")
168
+
169
+ block_size = meta["config"]["block_size"]
170
+ history = []
171
+
172
+ def respond(user_text):
173
+ piece = tokenizer.encode("\nAnda: " + user_text + "\nIndigo:")
174
+ context = (history + piece)[-block_size:]
175
+ prev_history = list(history)
176
+ prompt_nll = mean_nll(logp_fn, [], piece, block_size)
177
+ out = generate_fn(model, idx_input(context), args.max_new, block_size,
178
+ temperature=args.temperature, top_k=args.top_k)
179
+ full = (out[0].tolist() if hasattr(out[0], "tolist") else out[0])
180
+ new_ids = full[len(context):]
181
+ text = tokenizer.decode(new_ids).strip()
182
+ ratio = word_known_ratio(text, wordset, prefiks, sufiks) if wordset else 1.0
183
+ guard_ok = ratio >= args.guard_min if wordset else True
184
+ if args.fallback and (not text or prompt_nll > args.threshold or not guard_ok):
185
+ history[:] = prev_history
186
+ return args.fallback, prompt_nll
187
+ history.clear()
188
+ history.extend(full[-block_size:])
189
+ return text, prompt_nll
190
+
191
+ while True:
192
+ try:
193
+ user = input("\nAnda> ").strip()
194
+ except (EOFError, KeyboardInterrupt):
195
+ print()
196
+ break
197
+ if not user:
198
+ continue
199
+ if user in ("/keluar", "/quit", "/exit"):
200
+ break
201
+ if user == "/reset":
202
+ history.clear()
203
+ print("(konteks direset)")
204
+ continue
205
+ reply, score = respond(user)
206
+ suffix = f" [nll={score:.2f}]" if args.show_nll else ""
207
+ print(f"Indigo> {reply}{suffix}")
208
+
209
+
210
+ if __name__ == "__main__":
211
+ main()