#!/usr/bin/env python3 """ infer_bytefast60m.py Correct standalone inference utility for the custom FastDeepHybridLM defined by bytefalcon_fast60m.py. This does NOT instantiate Falcon-H1 or any Hugging Face AutoModel class. It imports the exact training architecture and calls its load_model_bundle(), which reconstructs Fast60MConfig + FastDeepHybridLM and strictly loads model.pt. Expected checkpoint: step-XXXXXXXX/ config.json model.pt tokenizer.json tokenizer_config.json ... Rewrite training format: instruction "source text" "target output" For inference, rewrite mode supplies the opening output quote and lets the model generate the target text, closing quote, and EOS. """ from __future__ import annotations import argparse import contextlib import importlib.util import json import os import re import sys import time from pathlib import Path from types import ModuleType from typing import Any CONTEXT_LENGTH = 4096 # Match the training runtime setup before importing the architecture module. os.environ.setdefault("USE_HUB_KERNELS", "NO") os.environ.setdefault("PYTORCH_ALLOC_CONF", "expandable_segments:True") os.environ.setdefault("TOKENIZERS_PARALLELISM", "false") os.environ.setdefault("USE_ROCM_CK_GEMM", "1") os.environ.pop("PYTORCH_HIP_ALLOC_CONF", None) def is_checkpoint(path: Path) -> bool: return ( path.is_dir() and (path / "config.json").is_file() and (path / "model.pt").is_file() ) def checkpoint_rank(path: Path) -> tuple[int, float, str]: matches = re.findall(r"\d+", path.name) step = int(matches[-1]) if matches else -1 try: modified = path.stat().st_mtime except OSError: modified = 0.0 return step, modified, path.name def resolve_checkpoint( path: Path, *, extra_bases: list[Path] | None = None, ) -> Path: """ Accept an exact checkpoint, a run directory, or its checkpoints directory. Relative paths are searched from: 1. the current working directory; 2. the inference script directory; 3. any supplied extra bases, such as the architecture script directory. Preference within each candidate: exact directory -> final/ -> newest immediate checkpoint -> newest run/checkpoints checkpoint -> initial/ """ raw_path = path.expanduser() bases = [ Path.cwd(), Path(__file__).resolve().parent, ] if extra_bases: bases.extend(base.expanduser().resolve() for base in extra_bases) candidate_roots: list[Path] = [] if raw_path.is_absolute(): candidate_roots.append(raw_path.resolve()) else: candidate_roots.extend( (base / raw_path).resolve() for base in bases ) candidate_roots = list(dict.fromkeys(candidate_roots)) inspected: list[dict[str, Any]] = [] def inspect_root(root: Path) -> Path | None: inspected.append( { "candidate_root": str(root), "exists": root.exists(), "is_directory": root.is_dir(), } ) if is_checkpoint(root): return root final = root / "final" if is_checkpoint(final): return final direct = ( sorted( ( child for child in root.iterdir() if child.is_dir() and is_checkpoint(child) ), key=checkpoint_rank, ) if root.is_dir() else [] ) if direct: return direct[-1] checkpoint_root = root / "checkpoints" nested = ( sorted( ( child for child in checkpoint_root.iterdir() if child.is_dir() and is_checkpoint(child) ), key=checkpoint_rank, ) if checkpoint_root.is_dir() else [] ) if nested: return nested[-1] initial = root / "initial" if is_checkpoint(initial): print( "WARNING: using initial/; this is an untrained model.", file=sys.stderr, ) return initial for directory in (root, checkpoint_root): if not directory.is_dir(): continue for child in sorted(directory.iterdir()): if not child.is_dir(): continue inspected.append( { "path": str(child), "has_config": (child / "config.json").is_file(), "has_model_pt": (child / "model.pt").is_file(), "files": sorted( item.name for item in child.iterdir() if item.is_file() )[:50], } ) return None for candidate_root in candidate_roots: resolved = inspect_root(candidate_root) if resolved is not None: print( f"Resolved model path from {candidate_root}", file=sys.stderr, ) return resolved raise FileNotFoundError( "Could not find a FastDeepHybridLM checkpoint containing both " "config.json and model.pt.\n" "The supplied --model path was searched relative to the working " "directory, inference-script directory, and architecture-script " "directory.\n" + json.dumps(inspected, indent=2) + "\n\nCurrent working directory: " + str(Path.cwd()) + "\nInference script directory: " + str(Path(__file__).resolve().parent) ) def find_architecture_script(explicit: Path | None) -> Path: if explicit is not None: path = explicit.expanduser().resolve() if not path.is_file(): raise FileNotFoundError( f"Architecture script does not exist: {path}" ) return path here = Path(__file__).resolve().parent cwd = Path.cwd() candidates = [ cwd / "bytefalcon_fast60m.py", cwd / "bytefalcon.py", here / "bytefalcon_fast60m.py", here / "bytefalcon.py", ] for candidate in candidates: if not candidate.is_file(): continue source = candidate.read_text( encoding="utf-8", errors="replace", ) required = ( "class Fast60MConfig", "def create_model_classes", "def load_model_bundle", ) if all(marker in source for marker in required): return candidate.resolve() raise FileNotFoundError( "Could not locate the custom architecture script. Pass it explicitly:\n" " --architecture-script /path/to/bytefalcon_fast60m.py" ) def load_architecture_module(path: Path) -> ModuleType: module_name = "_bytefast60m_architecture" specification = importlib.util.spec_from_file_location( module_name, path, ) if specification is None or specification.loader is None: raise RuntimeError( f"Could not create an import specification for {path}" ) module = importlib.util.module_from_spec(specification) # Dataclasses and some runtime machinery expect the module to be present. sys.modules[module_name] = module specification.loader.exec_module(module) required = ( "Fast60MConfig", "create_model_classes", "import_training_stack", "load_model_bundle", "load_tokenizer", ) missing = [ name for name in required if not hasattr(module, name) ] if missing: raise RuntimeError( f"{path} is not the FastDeepHybridLM training script; " f"missing definitions: {missing}" ) return module def resolve_tokenizer( checkpoint: Path, explicit: Path | None, ) -> Path: candidates: list[Path] = [] if explicit is not None: candidates.append(explicit.expanduser().resolve()) candidates.extend( [ checkpoint, checkpoint / "tokenizer", ] ) project = Path(__file__).resolve().parent candidates.extend( [ project / "artifacts" / "byte-tokenizer", Path.cwd() / "artifacts" / "byte-tokenizer", ] ) for parent in list(checkpoint.parents)[:5]: candidates.extend( [ parent / "artifacts" / "byte-tokenizer", parent / "byte-tokenizer", ] ) candidates = list(dict.fromkeys(candidates)) for candidate in candidates: if ( candidate.is_dir() and ( (candidate / "tokenizer.json").is_file() or (candidate / "tokenizer.model").is_file() ) ): return candidate raise FileNotFoundError( "Tokenizer not found. Pass --tokenizer explicitly. Checked:\n" + "\n".join(f" - {path}" for path in candidates) ) def rewrite_prompt(instruction: str, source_text: str) -> str: instruction = instruction.strip() source = '"' + source_text + '"' if instruction: return instruction + "\n\n" + source + '\n\n"' return source + '\n\n"' def control_token_id_map( tokenizer: Any, architecture: ModuleType, ) -> dict[str, int]: control_tokens = getattr( architecture, "CONTROL_TOKENS", [ "", "", "", "", "", "", "", "", "", "", ], ) result = {} for token in control_tokens: token_id = tokenizer.convert_tokens_to_ids(token) if token_id is None: continue token_id = int(token_id) if token_id >= 0: result[token] = token_id return result def blocked_generation_ids( tokenizer: Any, architecture: ModuleType, ) -> list[int]: mapping = control_token_id_map(tokenizer, architecture) return sorted( token_id for token, token_id in mapping.items() if token != "" ) def apply_repetition_penalty( torch: Any, logits: Any, input_ids: Any, penalty: float, ) -> Any: if penalty == 1.0: return logits used = torch.unique(input_ids) selected = logits[:, used] logits[:, used] = torch.where( selected < 0, selected * penalty, selected / penalty, ) return logits def sample_next_token( torch: Any, logits: Any, *, temperature: float, top_k: int, top_p: float, ) -> Any: if temperature <= 0: return logits.argmax(dim=-1, keepdim=True) logits = logits / max(temperature, 1e-5) if top_k > 0: top_k = min(top_k, logits.shape[-1]) threshold = torch.topk( logits, top_k, dim=-1, ).values[:, -1:] logits = logits.masked_fill( logits < threshold, -float("inf"), ) probabilities = torch.softmax(logits, dim=-1) if top_p < 1.0: sorted_probabilities, sorted_indices = torch.sort( probabilities, descending=True, dim=-1, ) cumulative = sorted_probabilities.cumsum(dim=-1) remove = cumulative > top_p remove[:, 1:] = remove[:, :-1].clone() remove[:, 0] = False sorted_probabilities = sorted_probabilities.masked_fill( remove, 0.0, ) denominator = sorted_probabilities.sum( dim=-1, keepdim=True, ).clamp_min(1e-12) sorted_probabilities = ( sorted_probabilities / denominator ) sampled = torch.multinomial( sorted_probabilities, num_samples=1, ) return sorted_indices.gather(-1, sampled) return torch.multinomial(probabilities, num_samples=1) def clean_completion(text: str, rewrite_mode: bool) -> str: for marker in ("", "", ""): position = text.find(marker) if position >= 0: text = text[:position] if rewrite_mode: text = text.rstrip() if text.endswith('"'): text = text[:-1] return text def generate( *, architecture: ModuleType, checkpoint: Path, tokenizer_path: Path, prompt: str, max_new_tokens: int, temperature: float, top_k: int, top_p: float, repetition_penalty: float, seed: int, compile_model: bool, compile_mode: str, stream: bool, rewrite_mode: bool, allow_control_tokens: bool, show_top_tokens: int, ) -> tuple[str, dict[str, Any]]: ( _np, torch, nn, F, _DataLoader, _Dataset, ) = architecture.import_training_stack() if not torch.cuda.is_available(): raise RuntimeError( "ROCm PyTorch did not expose the AMD GPU through torch.cuda." ) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) device = torch.device("cuda") tokenizer = architecture.load_tokenizer(tokenizer_path) model = architecture.load_model_bundle( checkpoint, torch, nn, F, ) model.to(device) model.eval() blocked_ids = ( [] if allow_control_tokens else blocked_generation_ids(tokenizer, architecture) ) blocked_tensor = ( torch.tensor( blocked_ids, device=device, dtype=torch.long, ) if blocked_ids else None ) active_model = model if compile_model: active_model = torch.compile( model, mode=compile_mode, fullgraph=False, dynamic=False, ) encoded = tokenizer( prompt, add_special_tokens=False, return_tensors="pt", return_token_type_ids=False, ) input_ids = encoded.input_ids.to(device) prompt_tokens = int(input_ids.shape[1]) maximum_context = int( model.config.max_position_embeddings ) if prompt_tokens >= maximum_context: raise ValueError( f"Prompt has {prompt_tokens} tokens and exceeds the " f"{maximum_context}-token context." ) max_new_tokens = min( max_new_tokens, maximum_context - prompt_tokens, ) eos_id = int(tokenizer.eos_token_id) generated_ids: list[int] = [] torch.cuda.synchronize() torch.cuda.reset_peak_memory_stats() started = time.perf_counter() # This architecture has no KV/conv recurrent inference cache. It therefore # recomputes the active prefix each step, matching the original CLI. with torch.inference_mode(): for _ in range(max_new_tokens): model_input = input_ids[ :, -maximum_context: ] with torch.autocast( device_type="cuda", dtype=torch.bfloat16, enabled=True, ): logits = active_model( input_ids=model_input, return_last_logits=True, ).logits[:, -1, :] if not bool(torch.isfinite(logits).all().item()): print( "Non-finite BF16 logits; retrying this token in FP32.", file=sys.stderr, ) with torch.autocast( device_type="cuda", enabled=False, ): logits = model( input_ids=model_input, return_last_logits=True, ).logits[:, -1, :].float() if not bool(torch.isfinite(logits).all().item()): logits = torch.nan_to_num( logits, nan=-float("inf"), posinf=1e4, neginf=-1e4, ) if show_top_tokens > 0: top_values, top_indices = torch.topk( logits, min(show_top_tokens, logits.shape[-1]), dim=-1, ) report = [ { "id": int(token_id), "token": tokenizer.decode( [int(token_id)], skip_special_tokens=False, clean_up_tokenization_spaces=False, ), "logit": float(value), } for token_id, value in zip( top_indices[0].tolist(), top_values[0].float().tolist(), ) ] print( "raw top tokens: " + json.dumps(report, ensure_ascii=False), file=sys.stderr, ) if blocked_tensor is not None: logits.index_fill_( 1, blocked_tensor, -float("inf"), ) logits = apply_repetition_penalty( torch, logits, model_input, repetition_penalty, ) if not bool(torch.isfinite(logits).any().item()): next_token = torch.tensor( [[int(tokenizer.eos_token_id)]], device=device, dtype=torch.long, ) else: next_token = sample_next_token( torch, logits, temperature=temperature, top_k=top_k, top_p=top_p, ) token_id = int(next_token.item()) if token_id in blocked_ids: raise RuntimeError( "A reserved control token escaped masking: " f"id={token_id}, token={tokenizer.decode([token_id], skip_special_tokens=False)!r}" ) generated_ids.append(token_id) input_ids = torch.cat( (input_ids, next_token), dim=-1, ) if stream: piece = tokenizer.decode( [token_id], skip_special_tokens=False, clean_up_tokenization_spaces=False, ) print(piece, end="", flush=True) if token_id == eos_id: break torch.cuda.synchronize() elapsed = time.perf_counter() - started raw_completion = tokenizer.decode( generated_ids, skip_special_tokens=False, clean_up_tokenization_spaces=False, ) completion = clean_completion( raw_completion, rewrite_mode, ) if stream: print() metrics = { "architecture": model.config.architecture, "model_type": model.config.model_type, "checkpoint": str(checkpoint), "tokenizer": str(tokenizer_path), "parameters": sum( parameter.numel() for parameter in model.parameters() ), "prompt_tokens": prompt_tokens, "generated_tokens": len(generated_ids), "elapsed_seconds": elapsed, "tokens_per_second": ( len(generated_ids) / elapsed if elapsed > 0 else None ), "peak_vram_gib": ( torch.cuda.max_memory_allocated() / (1024**3) ), "compiled": compile_model, "blocked_control_token_ids": blocked_ids, "note": ( "Generation recomputes the active prefix because this custom " "architecture does not implement an incremental inference cache." ), } return completion, metrics def build_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser( description=( "Inference for the custom byte-deep-hybrid FastDeepHybridLM." ), formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) parser.add_argument( "--model", type=Path, default=Path("runs/bytefast-60m"), help=( "Exact checkpoint, run directory, or checkpoints directory. " "Relative paths are searched from the shell, script, and " "architecture-script directories." ), ) parser.add_argument( "--architecture-script", type=Path, help=( "Path to bytefalcon_fast60m.py. Automatically discovered " "when omitted." ), ) parser.add_argument( "--tokenizer", type=Path, help=( "Tokenizer directory. The checkpoint tokenizer is preferred." ), ) input_group = parser.add_mutually_exclusive_group(required=True) input_group.add_argument( "--prompt", help="Raw language-model prompt.", ) input_group.add_argument( "--text", help="Source text for rewrite mode.", ) parser.add_argument( "--instruction", default="Rewrite this clearly and naturally.", help="Instruction used with --text.", ) parser.add_argument("--max-new-tokens", type=int, default=128) parser.add_argument("--temperature", type=float, default=0.7) parser.add_argument("--top-p", type=float, default=0.95) parser.add_argument("--top-k", type=int, default=50) parser.add_argument( "--repetition-penalty", type=float, default=1.1, ) parser.add_argument("--seed", type=int, default=42) parser.add_argument( "--stream", action=argparse.BooleanOptionalAction, default=True, ) parser.add_argument( "--metrics", action=argparse.BooleanOptionalAction, default=True, ) parser.add_argument("--compile", action="store_true") parser.add_argument( "--compile-mode", choices=[ "default", "reduce-overhead", "max-autotune", ], default="reduce-overhead", ) parser.add_argument( "--show-prompt", action="store_true", ) parser.add_argument( "--allow-control-tokens", action="store_true", help="Allow structural tokens such as ; disabled by default.", ) parser.add_argument( "--show-top-tokens", type=int, default=0, help="Print the raw top-N logits before control-token masking.", ) return parser def validate_args(args: argparse.Namespace) -> None: if args.max_new_tokens <= 0: raise ValueError("--max-new-tokens must be positive.") if args.temperature < 0: raise ValueError("--temperature cannot be negative.") if not 0 < args.top_p <= 1: raise ValueError("--top-p must be in (0, 1].") if args.top_k < 0: raise ValueError("--top-k cannot be negative.") if args.repetition_penalty <= 0: raise ValueError( "--repetition-penalty must be positive." ) if args.show_top_tokens < 0: raise ValueError("--show-top-tokens must be non-negative.") def main() -> int: args = build_parser().parse_args() validate_args(args) architecture_path = find_architecture_script( args.architecture_script ) checkpoint = resolve_checkpoint( args.model, extra_bases=[architecture_path.parent], ) architecture = load_architecture_module( architecture_path ) tokenizer_path = resolve_tokenizer( checkpoint, args.tokenizer, ) rewrite_mode = args.text is not None prompt = ( rewrite_prompt(args.instruction, args.text) if rewrite_mode else args.prompt ) assert prompt is not None print( json.dumps( { "checkpoint": str(checkpoint), "architecture_script": str(architecture_path), "tokenizer": str(tokenizer_path), "rewrite_mode": rewrite_mode, }, indent=2, ), file=sys.stderr, ) if args.show_prompt: print( "----- PROMPT -----\n" + prompt + "\n----- END PROMPT -----", file=sys.stderr, ) completion, metrics = generate( architecture=architecture, checkpoint=checkpoint, tokenizer_path=tokenizer_path, prompt=prompt, max_new_tokens=args.max_new_tokens, temperature=args.temperature, top_k=args.top_k, top_p=args.top_p, repetition_penalty=args.repetition_penalty, seed=args.seed, compile_model=args.compile, compile_mode=args.compile_mode, stream=args.stream, rewrite_mode=rewrite_mode, allow_control_tokens=args.allow_control_tokens, show_top_tokens=args.show_top_tokens, ) if not args.stream: print(completion) if args.metrics: print( "\n" + json.dumps(metrics, indent=2), file=sys.stderr, ) return 0 if __name__ == "__main__": try: raise SystemExit(main()) except KeyboardInterrupt: print("\nInterrupted.", file=sys.stderr) raise SystemExit(130)