Download inference.py from KiranAN1988/NeemSutra-125M-Python-Instruct-Beta: direct link, hf CLI and curl.
- Browser
- Download file 10.4 kB
-
https://huggingface.co/KiranAN1988/NeemSutra-125M-Python-Instruct-Beta/resolve/main/inference.py
- Command line
-
hf download hf://KiranAN1988/NeemSutra-125M-Python-Instruct-Beta/inference.py
-
curl -L -o inference.py https://huggingface.co/KiranAN1988/NeemSutra-125M-Python-Instruct-Beta/resolve/main/inference.py
10.4 kB
| import argparse | |
| import random | |
| from pathlib import Path | |
| import torch | |
| from model.sutra import SutraModel | |
| from tokenizer import SutraTokenizerFast | |
| # ============================================================ | |
| # NeemSutra-125M-Python-Instruct | |
| # Reproducible inference script | |
| # ============================================================ | |
| ROOT = Path(__file__).resolve().parent | |
| CHECKPOINT = ROOT / "neemsutra_125m_python_instruct.pt" | |
| TOKENIZER_DIR = ROOT | |
| # Exact architecture of the released model | |
| VOCAB_SIZE = 8000 | |
| CONTEXT_LENGTH = 256 | |
| D_MODEL = 768 | |
| N_LAYERS = 16 | |
| N_HEADS = 12 | |
| # Training used dropout, but inference must disable it. | |
| DROPOUT = 0.0 | |
| def get_device(requested): | |
| if requested == "cpu": | |
| return torch.device("cpu") | |
| if requested == "cuda": | |
| if not torch.cuda.is_available(): | |
| raise RuntimeError("CUDA was requested but is not available.") | |
| return torch.device("cuda") | |
| if requested == "mps": | |
| if not ( | |
| hasattr(torch.backends, "mps") | |
| and torch.backends.mps.is_available() | |
| ): | |
| raise RuntimeError("MPS was requested but is not available.") | |
| return torch.device("mps") | |
| # auto | |
| if torch.cuda.is_available(): | |
| return torch.device("cuda") | |
| if hasattr(torch.backends, "mps") and torch.backends.mps.is_available(): | |
| return torch.device("mps") | |
| return torch.device("cpu") | |
| def set_seed(seed): | |
| random.seed(seed) | |
| torch.manual_seed(seed) | |
| if torch.cuda.is_available(): | |
| torch.cuda.manual_seed_all(seed) | |
| def load_model(device): | |
| if not CHECKPOINT.exists(): | |
| raise FileNotFoundError( | |
| f"Checkpoint not found:\n{CHECKPOINT}" | |
| ) | |
| model = SutraModel( | |
| vocab_size=VOCAB_SIZE, | |
| context_length=CONTEXT_LENGTH, | |
| d_model=D_MODEL, | |
| n_layers=N_LAYERS, | |
| n_heads=N_HEADS, | |
| dropout=DROPOUT, | |
| ) | |
| checkpoint = torch.load( | |
| CHECKPOINT, | |
| map_location="cpu", | |
| weights_only=False, | |
| ) | |
| # -------------------------------------------------------- | |
| # Support common checkpoint formats | |
| # -------------------------------------------------------- | |
| if isinstance(checkpoint, dict): | |
| if "model_state_dict" in checkpoint: | |
| state_dict = checkpoint["model_state_dict"] | |
| elif "state_dict" in checkpoint: | |
| state_dict = checkpoint["state_dict"] | |
| elif "model" in checkpoint and isinstance( | |
| checkpoint["model"], dict | |
| ): | |
| state_dict = checkpoint["model"] | |
| else: | |
| # The checkpoint itself may be a state_dict. | |
| state_dict = checkpoint | |
| else: | |
| raise RuntimeError( | |
| f"Unsupported checkpoint type: {type(checkpoint)}" | |
| ) | |
| # -------------------------------------------------------- | |
| # Remove DDP "module." prefix if present | |
| # -------------------------------------------------------- | |
| cleaned_state_dict = {} | |
| for key, value in state_dict.items(): | |
| if key.startswith("module."): | |
| key = key[len("module."):] | |
| cleaned_state_dict[key] = value | |
| missing, unexpected = model.load_state_dict( | |
| cleaned_state_dict, | |
| strict=False, | |
| ) | |
| if missing: | |
| raise RuntimeError( | |
| "Missing model parameters:\n" | |
| + "\n".join(missing) | |
| ) | |
| if unexpected: | |
| print( | |
| "Warning: unexpected checkpoint parameters:" | |
| ) | |
| for key in unexpected: | |
| print(f" {key}") | |
| model = model.to(device) | |
| model.eval() | |
| return model | |
| def sample_top_k(logits, top_k): | |
| if top_k <= 0: | |
| return logits | |
| k = min(top_k, logits.size(-1)) | |
| values, _ = torch.topk(logits, k) | |
| threshold = values[..., -1, None] | |
| return torch.where( | |
| logits < threshold, | |
| torch.full_like(logits, float("-inf")), | |
| logits, | |
| ) | |
| def sample_top_p(logits, top_p): | |
| if top_p >= 1.0: | |
| return logits | |
| if top_p <= 0.0: | |
| return logits | |
| sorted_logits, sorted_indices = torch.sort( | |
| logits, | |
| descending=True, | |
| ) | |
| probabilities = torch.softmax( | |
| sorted_logits, | |
| dim=-1, | |
| ) | |
| cumulative_probabilities = torch.cumsum( | |
| probabilities, | |
| dim=-1, | |
| ) | |
| remove = cumulative_probabilities > top_p | |
| # Always retain the highest-probability token. | |
| remove[..., 0] = False | |
| sorted_logits = sorted_logits.masked_fill( | |
| remove, | |
| float("-inf"), | |
| ) | |
| filtered = torch.full_like(logits, float("-inf")) | |
| filtered.scatter_( | |
| -1, | |
| sorted_indices, | |
| sorted_logits, | |
| ) | |
| return filtered | |
| def generate( | |
| model, | |
| tokenizer, | |
| prompt, | |
| max_new_tokens=40, | |
| temperature=0.0, | |
| top_k=0, | |
| top_p=1.0, | |
| seed=42, | |
| ): | |
| set_seed(seed) | |
| input_ids = tokenizer.encode(prompt) | |
| if not input_ids: | |
| raise ValueError("Prompt produced no tokens.") | |
| if len(input_ids) > CONTEXT_LENGTH: | |
| print( | |
| f"Warning: prompt contains {len(input_ids)} tokens. " | |
| f"Truncating to the last {CONTEXT_LENGTH} tokens." | |
| ) | |
| input_ids = input_ids[-CONTEXT_LENGTH:] | |
| tokens = torch.tensor( | |
| [input_ids], | |
| dtype=torch.long, | |
| device=next(model.parameters()).device, | |
| ) | |
| generated = list(input_ids) | |
| eos_id = tokenizer.eos_token_id | |
| for _ in range(max_new_tokens): | |
| # Keep only the model's available context. | |
| context = tokens[:, -CONTEXT_LENGTH:] | |
| logits = model(context) | |
| # Prediction for the final input position. | |
| next_token_logits = logits[:, -1, :] | |
| # ---------------------------------------------------- | |
| # Greedy / deterministic generation | |
| # ---------------------------------------------------- | |
| if temperature <= 0.0: | |
| next_token = torch.argmax( | |
| next_token_logits, | |
| dim=-1, | |
| keepdim=True, | |
| ) | |
| else: | |
| scaled_logits = ( | |
| next_token_logits / temperature | |
| ) | |
| scaled_logits = sample_top_k( | |
| scaled_logits, | |
| top_k, | |
| ) | |
| scaled_logits = sample_top_p( | |
| scaled_logits, | |
| top_p, | |
| ) | |
| probabilities = torch.softmax( | |
| scaled_logits, | |
| dim=-1, | |
| ) | |
| next_token = torch.multinomial( | |
| probabilities, | |
| num_samples=1, | |
| ) | |
| token_id = next_token.item() | |
| generated.append(token_id) | |
| # Append token for next iteration. | |
| tokens = torch.cat( | |
| [tokens, next_token], | |
| dim=1, | |
| ) | |
| if eos_id is not None and token_id == eos_id: | |
| break | |
| # Decode only the generated continuation. | |
| continuation = generated[len(input_ids):] | |
| return tokenizer.decode( | |
| continuation, | |
| skip_special_tokens=True, | |
| ) | |
| def main(): | |
| parser = argparse.ArgumentParser( | |
| description="NeemSutra-125M-Python-Instruct inference" | |
| ) | |
| parser.add_argument( | |
| "--prompt", | |
| type=str, | |
| required=True, | |
| help="Input prompt.", | |
| ) | |
| parser.add_argument( | |
| "--max-new-tokens", | |
| type=int, | |
| default=40, | |
| help="Maximum number of generated tokens.", | |
| ) | |
| parser.add_argument( | |
| "--temperature", | |
| type=float, | |
| default=0.0, | |
| help="Sampling temperature. 0 = greedy/deterministic.", | |
| ) | |
| parser.add_argument( | |
| "--top-k", | |
| type=int, | |
| default=0, | |
| help="Top-k sampling. 0 disables top-k.", | |
| ) | |
| parser.add_argument( | |
| "--top-p", | |
| type=float, | |
| default=1.0, | |
| help="Nucleus sampling threshold.", | |
| ) | |
| parser.add_argument( | |
| "--seed", | |
| type=int, | |
| default=42, | |
| help="Random seed.", | |
| ) | |
| parser.add_argument( | |
| "--device", | |
| choices=["auto", "cpu", "cuda", "mps"], | |
| default="auto", | |
| help="Inference device.", | |
| ) | |
| args = parser.parse_args() | |
| if args.max_new_tokens < 1: | |
| raise ValueError( | |
| "--max-new-tokens must be greater than 0." | |
| ) | |
| if args.temperature < 0: | |
| raise ValueError( | |
| "--temperature cannot be negative." | |
| ) | |
| if not 0 < args.top_p <= 1: | |
| raise ValueError( | |
| "--top-p must be in the range (0, 1]." | |
| ) | |
| device = get_device(args.device) | |
| print("=" * 70) | |
| print(f"Checkpoint : {CHECKPOINT.name}") | |
| print(f"Device : {device}") | |
| print(f"Parameters : {sum(p.numel() for p in SutraModel( | |
| VOCAB_SIZE, CONTEXT_LENGTH, D_MODEL, N_LAYERS, N_HEADS, | |
| DROPOUT | |
| ).parameters()):,}") | |
| print(f"Temperature: {args.temperature}") | |
| print(f"Seed : {args.seed}") | |
| print("=" * 70) | |
| tokenizer = SutraTokenizerFast( | |
| TOKENIZER_DIR | |
| ) | |
| model = load_model(device) | |
| parameter_count = sum( | |
| p.numel() for p in model.parameters() | |
| ) | |
| print("=" * 70) | |
| print("NeemSutra-125M-Python-Instruct") | |
| print("=" * 70) | |
| print(f"Checkpoint : {CHECKPOINT.name}") | |
| print(f"Device : {device}") | |
| print(f"Parameters : {parameter_count:,}") | |
| print(f"Temperature: {args.temperature}") | |
| print(f"Seed : {args.seed}") | |
| print("=" * 70) | |
| print("\nPrompt:") | |
| print(args.prompt) | |
| output = generate( | |
| model=model, | |
| tokenizer=tokenizer, | |
| prompt=args.prompt, | |
| max_new_tokens=args.max_new_tokens, | |
| temperature=args.temperature, | |
| top_k=args.top_k, | |
| top_p=args.top_p, | |
| seed=args.seed, | |
| ) | |
| print("\nGenerated:") | |
| print("-" * 70) | |
| print(output) | |
| print("-" * 70) | |
| if __name__ == "__main__": | |
| main() |