# Copyright Lightning AI. Licensed under the Apache License 2.0, see LICENSE file. import sys import time import warnings from pathlib import Path from pprint import pprint from typing import Literal import lightning as L import torch from lightning.fabric.plugins import BitsandbytesPrecision from litgpt import PromptStyle, Tokenizer from litgpt.adapter import GPT, Config from litgpt.constants import _BITANDBYTES_AVAILABLE_NOT_EQUAL_0_42_0 from litgpt.generate.base import generate from litgpt.prompts import has_prompt_style, load_prompt_style from litgpt.utils import ( check_file_size_on_cpu_and_warn, check_valid_checkpoint_dir, extend_checkpoint_dir, get_default_supported_precision, lazy_load, ) def main( checkpoint_dir: Path, prompt: str = "What food do llamas eat?", input: str = "", sys_prompt: str | None = None, adapter_path: Path = Path("out/finetune/adapter/final/lit_model.pth.adapter"), quantize: Literal["bnb.nf4", "bnb.nf4-dq", "bnb.fp4", "bnb.fp4-dq", "bnb.int8"] | None = None, max_new_tokens: int = 100, top_k: int | None = 50, top_p: float = 1.0, temperature: float = 0.8, precision: str | None = None, ) -> None: """For models finetuned with `litgpt finetune_adapter`. Generates a response based on a given instruction and an optional input. This script will only work with checkpoints from the instruction-tuned adapter model. See ``litgpt.finetune.adapter``. Args: checkpoint_dir: The path to the checkpoint folder with pretrained model weights. prompt: The prompt/instruction (Alpaca style). input: Optional input (Alpaca style). sys_prompt: Optional system prompt. adapter_path: Path to the checkpoint with trained adapter weights, which are the output of ``litgpt.finetune.adapter``. quantize: Whether to quantize the model and using which method: - bnb.nf4, bnb.nf4-dq, bnb.fp4, bnb.fp4-dq: 4-bit quantization from bitsandbytes - bnb.int8: 8-bit quantization from bitsandbytes for more details, see https://github.com/Lightning-AI/litgpt/blob/main/tutorials/quantize.md max_new_tokens: The number of generation steps to take. top_k: The number of top most probable tokens to consider in the sampling process. top_p: If specified, it represents the cumulative probability threshold to consider in the sampling process. In top-p sampling, the next token is sampled from the highest probability tokens whose cumulative probability exceeds the threshold `top_p`. When specified, it must be `0 <= top_p <= 1`. Here, `top_p=0` is equivalent to sampling the most probable token, while `top_p=1` samples from the whole distribution. It can be used in conjunction with `top_k` and `temperature` with the following order of application: 1. `top_k` sampling 2. `temperature` scaling 3. `top_p` sampling For more details, see https://arxiv.org/abs/1904.09751 or https://huyenchip.com/2024/01/16/sampling.html#top_p temperature: A value controlling the randomness of the sampling process. Higher values result in more random samples. precision: Indicates the Fabric precision setting to use. """ checkpoint_dir = extend_checkpoint_dir(checkpoint_dir) pprint(locals()) precision = precision or get_default_supported_precision(training=False) plugins = None if quantize is not None and quantize.startswith("bnb."): if "mixed" in precision: raise ValueError("Quantization and mixed precision is not supported.") if _BITANDBYTES_AVAILABLE_NOT_EQUAL_0_42_0: warnings.warn( "LitGPT only supports bitsandbytes v0.42.0. This may result in errors when using quantization." ) dtype = {"16-true": torch.float16, "bf16-true": torch.bfloat16, "32-true": torch.float32}[precision] plugins = BitsandbytesPrecision(quantize[4:], dtype) precision = None fabric = L.Fabric(devices=1, precision=precision, plugins=plugins) fabric.launch() check_valid_checkpoint_dir(checkpoint_dir) config = Config.from_file(checkpoint_dir / "model_config.yaml") checkpoint_path = checkpoint_dir / "lit_model.pth" check_file_size_on_cpu_and_warn(checkpoint_path, fabric.device) tokenizer = Tokenizer(checkpoint_dir) prompt_style = ( load_prompt_style(checkpoint_dir) if has_prompt_style(checkpoint_dir) else PromptStyle.from_config(config) ) prompt = prompt_style.apply(prompt, sys_prompt=sys_prompt, input=input) encoded = tokenizer.encode(prompt, device=fabric.device) prompt_length = encoded.size(0) max_returned_tokens = prompt_length + max_new_tokens fabric.print(f"Loading model {str(checkpoint_path)!r} with {config.__dict__}", file=sys.stderr) t0 = time.perf_counter() with fabric.init_module(empty_init=True): model = GPT(config) fabric.print(f"Time to instantiate model: {time.perf_counter() - t0:.02f} seconds.", file=sys.stderr) with fabric.init_tensor(): # set the max_seq_length to limit the memory usage to what we need model.max_seq_length = max_returned_tokens # enable the kv cache model.set_kv_cache(batch_size=1) model.eval() t0 = time.perf_counter() checkpoint = lazy_load(checkpoint_path) adapter_checkpoint = lazy_load(adapter_path) checkpoint.update(adapter_checkpoint.get("model", adapter_checkpoint)) model.load_state_dict(checkpoint) fabric.print(f"Time to load the model weights: {time.perf_counter() - t0:.02f} seconds.", file=sys.stderr) model = fabric.setup(model) L.seed_everything(1234) t0 = time.perf_counter() y = generate( model, encoded, max_returned_tokens, temperature=temperature, top_k=top_k, top_p=top_p, eos_id=tokenizer.eos_id ) t = time.perf_counter() - t0 output = tokenizer.decode(y) output = output.split("### Response:")[1].strip() fabric.print(output) tokens_generated = y.size(0) - prompt_length fabric.print(f"\n\nTime for inference: {t:.02f} sec total, {tokens_generated / t:.02f} tokens/sec", file=sys.stderr) if fabric.device.type == "cuda": fabric.print(f"Memory used: {torch.cuda.max_memory_allocated() / 1e9:.02f} GB", file=sys.stderr)