KiranAN1988's picture
Initial beta release
2e30a77 verified
Raw History Blame Contribute Delete
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
@torch.no_grad()
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()