"""Load ANKA-50M RMW3 and generate text with the released checkpoint.""" from __future__ import annotations import argparse import json from pathlib import Path import torch from safetensors.torch import load_file from tokenizers import Tokenizer from modeling_anka import RMW3, RMW3Config, count_parameters def load_model(model_dir: str | Path, device: str | None = None): model_dir = Path(model_dir) device = device or ("cuda" if torch.cuda.is_available() else "cpu") config_data = json.loads((model_dir / "config.json").read_text()) config = RMW3Config( **{ field: config_data[field] for field in RMW3Config.__dataclass_fields__ } ) model = RMW3(config) dtype = torch.bfloat16 if device.startswith("cuda") else torch.float32 model = model.to(device=device, dtype=dtype) state_dict = load_file(str(model_dir / "model.safetensors"), device="cpu") model.load_state_dict(state_dict, strict=True) model.eval() if count_parameters(model) != 48_944_657: raise RuntimeError("Unexpected parameter count") if model.lm_head.weight.data_ptr() != model.token_embeddings.weight.data_ptr(): raise RuntimeError("Embedding/output weight tying is not active") tokenizer = Tokenizer.from_file(str(model_dir / "tokenizer.json")) return model, tokenizer @torch.inference_mode() def generate( model: RMW3, tokenizer: Tokenizer, prompt: str, max_new_tokens: int = 128, temperature: float = 0.8, top_p: float = 0.95, seed: int = 3407, ) -> str: device = next(model.parameters()).device token_ids = tokenizer.encode(prompt, add_special_tokens=False).ids or [1] generated: list[int] = [] generator = torch.Generator(device=device).manual_seed(seed) for _ in range(max_new_tokens): active = (token_ids + generated)[-model.config.max_seq_len :] input_ids = torch.tensor([active], device=device, dtype=torch.long) with torch.autocast( device_type=device.type, dtype=torch.bfloat16, enabled=device.type == "cuda", ): logits, _ = model(input_ids) next_logits = logits[0, -1].float() if temperature <= 0: next_id = int(next_logits.argmax()) else: probabilities = torch.softmax(next_logits / temperature, dim=-1) if top_p < 1.0: sorted_probs, sorted_ids = torch.sort(probabilities, descending=True) remove = (torch.cumsum(sorted_probs, dim=-1) - sorted_probs) > top_p sorted_probs[remove] = 0 sorted_probs /= sorted_probs.sum().clamp_min(1e-12) sampled = torch.multinomial(sorted_probs, 1, generator=generator) next_id = int(sorted_ids[sampled].item()) else: next_id = int(torch.multinomial(probabilities, 1, generator=generator)) if next_id == 2: break generated.append(next_id) completion = tokenizer.decode(generated, skip_special_tokens=True) return prompt + completion def main() -> None: parser = argparse.ArgumentParser() parser.add_argument("--model-dir", type=Path, default=Path(__file__).parent) parser.add_argument("--prompt", default="The future of artificial intelligence") parser.add_argument("--max-new-tokens", type=int, default=128) parser.add_argument("--temperature", type=float, default=0.8) parser.add_argument("--top-p", type=float, default=0.95) parser.add_argument("--seed", type=int, default=3407) args = parser.parse_args() model, tokenizer = load_model(args.model_dir) print( generate( model, tokenizer, args.prompt, args.max_new_tokens, args.temperature, args.top_p, args.seed, ) ) if __name__ == "__main__": main()