HimalayaGPT commited on
Commit
c9b6574
·
verified ·
1 Parent(s): 22f823e

Add standalone HF inference script (no nanochat dependency)

Browse files
Files changed (1) hide show
  1. run_standalone_inference.py +261 -0
run_standalone_inference.py ADDED
@@ -0,0 +1,261 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Standalone HF inference helper for HimalayaGPT models.
4
+
5
+ This script intentionally depends only on:
6
+ - torch
7
+ - transformers
8
+ - huggingface_hub
9
+
10
+ No nanochat repo internals are required at runtime.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import argparse
16
+ from pathlib import Path
17
+ from typing import List
18
+
19
+
20
+ DEFAULT_PROMPTS = [
21
+ "नेपालको राजधानी के हो?",
22
+ "दुई वाक्यमा हिमालको महत्व बताऊ।",
23
+ "Write a short paragraph about machine learning.",
24
+ "What is 17 * 19? Show quick mental math.",
25
+ "Write a Python function to compute Fibonacci numbers.",
26
+ ]
27
+
28
+
29
+ SPECIAL_TOKENS = [
30
+ "<|bos|>",
31
+ "<|user_start|>",
32
+ "<|user_end|>",
33
+ "<|assistant_start|>",
34
+ "<|assistant_end|>",
35
+ "<|python_start|>",
36
+ "<|python_end|>",
37
+ "<|output_start|>",
38
+ "<|output_end|>",
39
+ ]
40
+
41
+
42
+ def parse_args() -> argparse.Namespace:
43
+ p = argparse.ArgumentParser(description="Run robust HF inference for HimalayaGPT")
44
+ p.add_argument("--repo-id", default="himalaya-ai/himalayagpt-0.5b-it")
45
+ p.add_argument("--revision", default="main")
46
+ p.add_argument("--force-download", action="store_true", help="Force fresh snapshot download from HF")
47
+ p.add_argument("--prompt-style", choices=["auto", "chat", "plain"], default="auto")
48
+ p.add_argument("--dtype", choices=["auto", "float32", "bfloat16"], default="auto")
49
+ p.add_argument("--temperature", type=float, default=0.8)
50
+ p.add_argument("--top-k", type=int, default=50)
51
+ p.add_argument("--max-new-tokens", type=int, default=96)
52
+ p.add_argument("--seed", type=int, default=42)
53
+ p.add_argument("--device", choices=["auto", "cuda", "cpu"], default="auto")
54
+ p.add_argument("--prompts-file", default=None, help="Optional .txt file with one prompt per line")
55
+ return p.parse_args()
56
+
57
+
58
+ def _special_id(tokenizer, token: str) -> int | None:
59
+ special_map = getattr(tokenizer, "_special_to_id", None)
60
+ if isinstance(special_map, dict) and token in special_map:
61
+ tid = int(special_map[token])
62
+ if tid >= 0:
63
+ return tid
64
+ tid = tokenizer.convert_tokens_to_ids(token)
65
+ if tid is None:
66
+ return None
67
+ if tokenizer.unk_token_id is not None and tid == tokenizer.unk_token_id and token != tokenizer.unk_token:
68
+ return None
69
+ return int(tid)
70
+
71
+
72
+ def _strip_special(text: str) -> str:
73
+ out = text
74
+ for tok in SPECIAL_TOKENS:
75
+ out = out.replace(tok, "")
76
+ return out.strip()
77
+
78
+
79
+ def _load_prompts(prompts_file: str | None) -> List[str]:
80
+ if not prompts_file:
81
+ return list(DEFAULT_PROMPTS)
82
+ p = Path(prompts_file)
83
+ lines = [line.strip() for line in p.read_text(encoding="utf-8").splitlines() if line.strip()]
84
+ if not lines:
85
+ raise ValueError(f"No prompts in {p}")
86
+ return lines
87
+
88
+
89
+ def _choose_device(arg: str):
90
+ import torch
91
+
92
+ if arg == "cuda":
93
+ if not torch.cuda.is_available():
94
+ raise RuntimeError("CUDA requested but not available")
95
+ return torch.device("cuda")
96
+ if arg == "cpu":
97
+ return torch.device("cpu")
98
+ return torch.device("cuda" if torch.cuda.is_available() else "cpu")
99
+
100
+
101
+ def _choose_torch_dtype(dtype_arg: str, device):
102
+ import torch
103
+
104
+ if dtype_arg == "float32":
105
+ return torch.float32
106
+ if dtype_arg == "bfloat16":
107
+ return torch.bfloat16
108
+ # auto policy: prefer bf16 if supported, otherwise fp32 (avoid fp16 instability)
109
+ if device.type == "cuda" and torch.cuda.is_bf16_supported():
110
+ return torch.bfloat16
111
+ return torch.float32
112
+
113
+
114
+ def _resolve_prompt_style(style_arg: str, tokenizer) -> str:
115
+ if style_arg != "auto":
116
+ return style_arg
117
+ needed = ["<|user_start|>", "<|user_end|>", "<|assistant_start|>"]
118
+ if all(_special_id(tokenizer, tok) is not None for tok in needed):
119
+ return "chat"
120
+ return "plain"
121
+
122
+
123
+ def _build_prompt_ids(tokenizer, prompt: str, prompt_style: str, vocab_size: int) -> List[int]:
124
+ user_ids = tokenizer(prompt, add_special_tokens=False)["input_ids"]
125
+ if prompt_style == "plain":
126
+ ids = user_ids
127
+ else:
128
+ bos = _special_id(tokenizer, "<|bos|>")
129
+ u_s = _special_id(tokenizer, "<|user_start|>")
130
+ u_e = _special_id(tokenizer, "<|user_end|>")
131
+ a_s = _special_id(tokenizer, "<|assistant_start|>")
132
+ if None in (bos, u_s, u_e, a_s):
133
+ raise RuntimeError("Chat style requested but special tokens are missing")
134
+ ids = [int(bos), int(u_s)] + user_ids + [int(u_e), int(a_s)]
135
+
136
+ # hard clamp safety against malformed token ids
137
+ return [min(max(int(t), 0), vocab_size - 1) for t in ids]
138
+
139
+
140
+ def _top_k_filter(logits, top_k: int):
141
+ import torch
142
+
143
+ if top_k <= 0:
144
+ return logits
145
+ k = min(top_k, logits.size(-1))
146
+ v, _ = torch.topk(logits, k)
147
+ masked = logits.clone()
148
+ masked[masked < v[:, [-1]]] = -float("inf")
149
+ return masked
150
+
151
+
152
+ def generate_compat(model, input_ids, max_new_tokens: int, temperature: float, top_k: int, seed: int):
153
+ import torch
154
+ import torch.nn.functional as F
155
+
156
+ ids = input_ids
157
+ rng = None
158
+ if temperature > 0:
159
+ rng = torch.Generator(device=ids.device)
160
+ rng.manual_seed(seed)
161
+
162
+ for _ in range(max_new_tokens):
163
+ attention_mask = torch.ones_like(ids)
164
+ logits = model(input_ids=ids, attention_mask=attention_mask, return_dict=True).logits[:, -1, :]
165
+ logits = _top_k_filter(logits, top_k)
166
+ if temperature > 0:
167
+ probs = F.softmax(logits / temperature, dim=-1)
168
+ next_ids = torch.multinomial(probs, num_samples=1, generator=rng)
169
+ else:
170
+ next_ids = torch.argmax(logits, dim=-1, keepdim=True)
171
+ ids = torch.cat((ids, next_ids), dim=1)
172
+ return ids
173
+
174
+
175
+ def main() -> None:
176
+ args = parse_args()
177
+
178
+ import torch
179
+ import transformers
180
+ from huggingface_hub import snapshot_download
181
+ from transformers import AutoModelForCausalLM, AutoTokenizer
182
+
183
+ if transformers.__version__ == "4.57.0":
184
+ print(
185
+ "[warn] transformers==4.57.0 is yanked on PyPI due to packaging issues. "
186
+ "Prefer transformers>=4.57.1."
187
+ )
188
+
189
+ prompts = _load_prompts(args.prompts_file)
190
+ device = _choose_device(args.device)
191
+ torch_dtype = _choose_torch_dtype(args.dtype, device)
192
+
193
+ local = snapshot_download(
194
+ repo_id=args.repo_id,
195
+ repo_type="model",
196
+ revision=args.revision,
197
+ force_download=args.force_download,
198
+ )
199
+ sha = Path(local).name
200
+
201
+ tok = AutoTokenizer.from_pretrained(local, trust_remote_code=True)
202
+ model = AutoModelForCausalLM.from_pretrained(
203
+ local,
204
+ trust_remote_code=True,
205
+ torch_dtype=torch_dtype,
206
+ device_map="auto" if device.type == "cuda" else None,
207
+ )
208
+ if device.type == "cpu":
209
+ model = model.to(device)
210
+ model.eval()
211
+
212
+ cfg = model.config
213
+ vocab_size = int(getattr(cfg, "padded_vocab_size", getattr(cfg, "vocab_size", len(tok))))
214
+ context_window = int(
215
+ min(
216
+ x
217
+ for x in [
218
+ getattr(cfg, "sequence_len", None),
219
+ getattr(cfg, "max_position_embeddings", None),
220
+ getattr(tok, "model_max_length", None),
221
+ ]
222
+ if isinstance(x, int) and x > 0
223
+ )
224
+ )
225
+
226
+ prompt_style = _resolve_prompt_style(args.prompt_style, tok)
227
+ max_prompt_tokens = max(1, context_window - args.max_new_tokens)
228
+
229
+ print(f"repo={args.repo_id} revision={args.revision} snapshot_sha={sha}")
230
+ print(f"device={device} dtype={torch_dtype} prompt_style={prompt_style}")
231
+ print(f"context_window={context_window} max_prompt_tokens={max_prompt_tokens}")
232
+
233
+ torch.manual_seed(args.seed)
234
+ if torch.cuda.is_available():
235
+ torch.cuda.manual_seed_all(args.seed)
236
+
237
+ for i, prompt in enumerate(prompts, 1):
238
+ p_ids = _build_prompt_ids(tok, prompt, prompt_style, vocab_size)[:max_prompt_tokens]
239
+ input_ids = torch.tensor([p_ids], dtype=torch.long, device=next(model.parameters()).device)
240
+
241
+ with torch.no_grad():
242
+ out = generate_compat(
243
+ model=model,
244
+ input_ids=input_ids,
245
+ max_new_tokens=args.max_new_tokens,
246
+ temperature=args.temperature,
247
+ top_k=args.top_k,
248
+ seed=args.seed + i,
249
+ )
250
+
251
+ completion_ids = out[0, input_ids.shape[1] :]
252
+ completion = tok.decode(completion_ids, skip_special_tokens=False)
253
+ completion = _strip_special(completion)
254
+
255
+ print(f"\n--- Prompt {i} ---")
256
+ print("Prompt:", prompt)
257
+ print("Completion:", completion if completion else "<empty>")
258
+
259
+
260
+ if __name__ == "__main__":
261
+ main()