Download inference.py from yhx12/LatentLoop: direct link, hf CLI and curl.
- Browser
- Download file 5.84 kB
-
https://huggingface.co/yhx12/LatentLoop/resolve/main/inference.py
- Command line
-
hf download hf://yhx12/LatentLoop/inference.py
-
curl -L -o inference.py https://huggingface.co/yhx12/LatentLoop/resolve/main/inference.py
5.84 kB
| """Load released Safetensors checkpoints and generate text with the native model.""" | |
| import argparse | |
| import json | |
| from pathlib import Path | |
| import torch | |
| from model import Config, GPT | |
| def load_pretrained(model_path, *, subfolder=None, device="cuda", revision=None, | |
| attention_backend="sdpa", moe_backend=None, mlp_backend="torch"): | |
| """Return (GPT, SentencePieceProcessor) from a local folder or HF repository. | |
| Parameters remain FP32. Use BF16 autocast on CUDA, as in the paper. The | |
| default SDPA/torch activation kernels do not require FlashAttention. | |
| """ | |
| from safetensors.torch import load_file | |
| import sentencepiece as spm | |
| if subfolder and (Path(subfolder).is_absolute() or ".." in Path(subfolder).parts): | |
| raise ValueError("subfolder must be a relative path within the repository") | |
| root = Path(model_path).expanduser() | |
| if not root.is_dir(): | |
| from huggingface_hub import snapshot_download | |
| prefix = subfolder.rstrip("/") + "/" if subfolder else "" | |
| root = Path(snapshot_download(str(model_path), revision=revision, | |
| allow_patterns=[prefix + "*"])) | |
| directory = root / subfolder if subfolder else root | |
| fields = json.loads((directory / "model_config.json").read_text()) | |
| target = torch.device(device) | |
| fields.update(attention_backend=attention_backend, | |
| moe_backend=moe_backend or ("grouped_mm" if target.type == "cuda" else "torch"), | |
| mlp_backend=mlp_backend) | |
| with torch.device("meta"): | |
| model = GPT(Config(**fields)) | |
| expected = {key: tuple(value.shape) for key, value in model.state_dict().items()} | |
| index = json.loads((directory / "model.safetensors.index.json").read_text()) | |
| weights = index["weight_map"] | |
| if set(weights) != set(expected): | |
| raise ValueError("Checkpoint tensor names differ from the configured model") | |
| seen = set() | |
| for name in sorted(set(weights.values())): | |
| if Path(name).name != name or not name.endswith(".safetensors"): | |
| raise ValueError("Invalid checkpoint shard filename") | |
| state = load_file(str(directory / name), device="cpu") | |
| declared = {key for key, shard in weights.items() if shard == name} | |
| if set(state) != declared or seen.intersection(state): | |
| raise ValueError("Missing, duplicated, or unexpected tensors in checkpoint shard") | |
| for key, value in state.items(): | |
| if tuple(value.shape) != expected[key] or value.dtype != torch.float32: | |
| raise ValueError(f"Unexpected shape or precision for {key}") | |
| model.load_state_dict(state, strict=False, assign=True) | |
| seen.update(state) | |
| del state | |
| if seen != set(expected) or any(p.is_meta for p in model.parameters()): | |
| raise ValueError("Checkpoint did not populate all model parameters") | |
| model = model.to(target).eval() | |
| tokenizer = spm.SentencePieceProcessor(model_file=str(directory / "tokenizer/tokenizer.model")) | |
| if tokenizer.vocab_size() != model.config.vocab_size: | |
| raise ValueError("Tokenizer vocabulary differs from the model") | |
| return model, tokenizer | |
| def generate(model, token_ids, *, max_new_tokens=64, eos_token_id=2): | |
| """Greedy continuation with each loop visit's own incremental KV cache.""" | |
| if token_ids.ndim != 2 or token_ids.size(0) != 1 or token_ids.size(1) == 0: | |
| raise ValueError("Expected a nonempty prompt with batch size one") | |
| if max_new_tokens <= 0 or token_ids.size(1) + max_new_tokens > model.config.block_size: | |
| raise ValueError("Prompt plus generated tokens must fit the model's context length") | |
| tokens = token_ids | |
| start = 0 | |
| model.reset_cache() | |
| try: | |
| for _ in range(max_new_tokens): | |
| segment = tokens[:, start:] | |
| positions = torch.arange(start, tokens.size(1), device=tokens.device) | |
| logits = model(segment, input_pos=positions) | |
| next_token = logits[:, -1].argmax(-1, keepdim=True) | |
| start = tokens.size(1) | |
| tokens = torch.cat((tokens, next_token), dim=1) | |
| if next_token.item() == eos_token_id: | |
| break | |
| return tokens | |
| finally: | |
| model.reset_cache() | |
| def main(): | |
| parser = argparse.ArgumentParser(description=__doc__) | |
| parser.add_argument("--model", default="yhx12/LatentLoop", help="HF repository ID or local folder") | |
| parser.add_argument("--subfolder", default="1.3B-40B", help="1.3B-40B, 3B-70B, or 7B-140B") | |
| parser.add_argument("--revision", help="Optional immutable HF commit") | |
| parser.add_argument("--prompt", default="The capital of France is") | |
| parser.add_argument("--max-new-tokens", type=int, default=64) | |
| parser.add_argument("--device", default="cuda") | |
| parser.add_argument("--attention-backend", choices=("sdpa", "flash"), default="sdpa") | |
| parser.add_argument("--moe-backend", choices=("torch", "grouped_mm")) | |
| parser.add_argument("--mlp-backend", choices=("torch", "flash"), default="torch") | |
| args = parser.parse_args() | |
| model, tokenizer = load_pretrained( | |
| args.model, subfolder=args.subfolder, device=args.device, revision=args.revision, | |
| attention_backend=args.attention_backend, moe_backend=args.moe_backend, | |
| mlp_backend=args.mlp_backend) | |
| prompt = tokenizer.encode(args.prompt, out_type=int) # No added BOS or chat template. | |
| tokens = torch.tensor([prompt], dtype=torch.long, device=args.device) | |
| with torch.autocast("cuda", dtype=torch.bfloat16, enabled=torch.device(args.device).type == "cuda"): | |
| output = generate(model, tokens, max_new_tokens=args.max_new_tokens, | |
| eos_token_id=tokenizer.eos_id()) | |
| print(tokenizer.decode(output[0].tolist())) | |
| if __name__ == "__main__": | |
| main() | |