#!/usr/bin/env python3 """Self-contained CPU/MPS greedy inference for the Fleck-S-100K Instruct export. This file intentionally has no import from the Fleck-LM source tree. The model architecture, chat template, and SafeTensors contract are reproduced here so this directory can be copied from Hugging Face and used on its own. """ from __future__ import annotations import argparse import math from collections.abc import Iterable from pathlib import Path from typing import cast import torch from safetensors import safe_open from safetensors.torch import load_file from torch import Tensor, nn from tokenizers import Tokenizer VOCAB_SIZE = 1024 CONTEXT_LENGTH = 2048 HIDDEN_SIZE = 64 INTERMEDIATE_SIZE = 128 PHYSICAL_BLOCKS = 2 EFFECTIVE_DEPTH = 4 QUERY_HEADS = 4 KV_HEADS = 2 HEAD_DIM = 16 EMBEDDING_RANK = 32 ROPE_THETA = 10000.0 BOS_ID, EOS_ID, PAD_ID, UNK_ID = 0, 1, 2, 3 SYSTEM_ID, USER_ID, ASSISTANT_ID, EOT_ID = 4, 5, 6, 7 EXECUTION_ORDER = (0, 1, 0, 1) TOKENIZER_NAME = "Fleck-Tokenizer-1024" MODEL_METADATA = { "candidate_id": "Fleck-S-100K", "canonical_dtype": "bfloat16", "context_length": "2048", "effective_depth": "4", "embedding_rank": "32", "head_dim": "16", "kv_heads": "2", "logical_execution_order": '["A", "B", "A", "B"]', "parameter_count": "109384", "physical_blocks": "2", "query_heads": "4", "tokenizer_name": TOKENIZER_NAME, } class RMSNorm(nn.Module): def __init__(self, size: int, eps: float = 1e-5) -> None: super().__init__() self.weight = nn.Parameter(torch.ones(size)) self.eps = eps def forward(self, hidden: Tensor) -> Tensor: variance = hidden.float().pow(2).mean(dim=-1, keepdim=True) scale = torch.rsqrt(variance + self.eps).to(dtype=hidden.dtype) return hidden * scale * self.weight class Attention(nn.Module): def __init__(self) -> None: super().__init__() self.q_proj = nn.Linear(HIDDEN_SIZE, QUERY_HEADS * HEAD_DIM, bias=False) self.k_proj = nn.Linear(HIDDEN_SIZE, KV_HEADS * HEAD_DIM, bias=False) self.v_proj = nn.Linear(HIDDEN_SIZE, KV_HEADS * HEAD_DIM, bias=False) self.o_proj = nn.Linear(HIDDEN_SIZE, HIDDEN_SIZE, bias=False) @staticmethod def _rope(value: Tensor, positions: Tensor) -> Tensor: half = HEAD_DIM // 2 frequencies = torch.arange(half, device=value.device, dtype=torch.float32) frequencies = ROPE_THETA ** (-2 * frequencies / HEAD_DIM) angles = positions.float().unsqueeze(-1) * frequencies cos = angles.cos().to(dtype=value.dtype)[None, None, :, :] sin = angles.sin().to(dtype=value.dtype)[None, None, :, :] first, second = value[..., :half], value[..., half:] return torch.cat((first * cos - second * sin, first * sin + second * cos), dim=-1) def forward( self, hidden: Tensor, positions: Tensor, past: tuple[Tensor, Tensor] | None = None, attention_mask: Tensor | None = None, ) -> tuple[Tensor, tuple[Tensor, Tensor]]: batch, length, _ = hidden.shape query = self.q_proj(hidden).view(batch, length, QUERY_HEADS, HEAD_DIM).transpose(1, 2) key = self.k_proj(hidden).view(batch, length, KV_HEADS, HEAD_DIM).transpose(1, 2) value = self.v_proj(hidden).view(batch, length, KV_HEADS, HEAD_DIM).transpose(1, 2) query = self._rope(query, positions) key = self._rope(key, positions) past_length = 0 if past is None else past[0].shape[2] if past is not None: key = torch.cat((past[0], key), dim=2) value = torch.cat((past[1], value), dim=2) total_length = key.shape[2] repeats = QUERY_HEADS // KV_HEADS expanded_key = key.repeat_interleave(repeats, dim=1) expanded_value = value.repeat_interleave(repeats, dim=1) scores = torch.matmul(query.float(), expanded_key.float().transpose(-1, -2)) scores = scores / math.sqrt(HEAD_DIM) query_positions = torch.arange( past_length, past_length + length, device=hidden.device )[:, None] key_positions = torch.arange(total_length, device=hidden.device)[None, :] causal = key_positions <= query_positions scores = scores.masked_fill( ~causal[None, None, :, :], torch.finfo(torch.float32).min ) if attention_mask is not None: if attention_mask.shape != (batch, total_length): raise ValueError("attention_mask must have shape (batch, complete_kv_length)") scores = scores.masked_fill( ~attention_mask.to(torch.bool)[:, None, None, :], torch.finfo(torch.float32).min ) probabilities = torch.softmax(scores, dim=-1, dtype=torch.float32) attended = torch.matmul(probabilities, expanded_value.float()).to(hidden.dtype) attended = attended.transpose(1, 2).contiguous().view(batch, length, HIDDEN_SIZE) return self.o_proj(attended), (key, value) class PhysicalBlock(nn.Module): def __init__(self) -> None: super().__init__() self.attention = Attention() self.mlp_gate = nn.Linear(HIDDEN_SIZE, INTERMEDIATE_SIZE, bias=False) self.mlp_up = nn.Linear(HIDDEN_SIZE, INTERMEDIATE_SIZE, bias=False) self.mlp_down = nn.Linear(INTERMEDIATE_SIZE, HIDDEN_SIZE, bias=False) class FleckModel(nn.Module): def __init__(self) -> None: super().__init__() self.token_embedding = nn.Parameter(torch.empty(VOCAB_SIZE, EMBEDDING_RANK)) self.embedding_projection = nn.Parameter(torch.empty(EMBEDDING_RANK, HIDDEN_SIZE)) self.blocks = nn.ModuleList(PhysicalBlock() for _ in range(PHYSICAL_BLOCKS)) self.attention_norms = nn.ModuleList( RMSNorm(HIDDEN_SIZE) for _ in range(EFFECTIVE_DEPTH) ) self.mlp_norms = nn.ModuleList(RMSNorm(HIDDEN_SIZE) for _ in range(EFFECTIVE_DEPTH)) self.depth_embeddings = nn.Parameter(torch.empty(EFFECTIVE_DEPTH, HIDDEN_SIZE)) self.attention_residual_scales = nn.Parameter(torch.empty(EFFECTIVE_DEPTH)) self.mlp_residual_scales = nn.Parameter(torch.empty(EFFECTIVE_DEPTH)) self.final_norm = RMSNorm(HIDDEN_SIZE) def _embed(self, input_ids: Tensor) -> Tensor: return torch.nn.functional.embedding(input_ids, self.token_embedding) @ self.embedding_projection def _logits(self, hidden: Tensor) -> Tensor: rank_hidden = hidden.float() @ self.embedding_projection.float().t() return rank_hidden @ self.token_embedding.float().t() def forward( self, input_ids: Tensor, *, past_key_values: tuple[tuple[Tensor, Tensor], ...] | None = None, attention_mask: Tensor | None = None, use_cache: bool = False, ) -> tuple[Tensor, tuple[tuple[Tensor, Tensor], ...] | None]: if input_ids.ndim != 2 or input_ids.shape[1] == 0: raise ValueError("input_ids must have shape (batch, non-empty sequence)") batch, length = input_ids.shape if past_key_values is not None and len(past_key_values) != EFFECTIVE_DEPTH: raise ValueError("past_key_values must contain one cache per effective depth") past_length = 0 if past_key_values is None else past_key_values[0][0].shape[2] if past_length + length > CONTEXT_LENGTH: raise ValueError(f"sequence exceeds context_length={CONTEXT_LENGTH}") positions = torch.arange(past_length, past_length + length, device=input_ids.device) hidden = self._embed(input_ids) caches: list[tuple[Tensor, Tensor]] = [] for depth, physical_index in enumerate(EXECUTION_ORDER): hidden = hidden + self.depth_embeddings[depth].view(1, 1, -1) block = cast(PhysicalBlock, self.blocks[physical_index]) attention_input = self.attention_norms[depth](hidden) past = None if past_key_values is None else past_key_values[depth] attended, cache = block.attention(attention_input, positions, past, attention_mask) hidden = hidden + self.attention_residual_scales[depth] * attended mlp_input = self.mlp_norms[depth](hidden) mlp_output = block.mlp_down( torch.nn.functional.silu(block.mlp_gate(mlp_input)) * block.mlp_up(mlp_input) ) hidden = hidden + self.mlp_residual_scales[depth] * mlp_output caches.append(cache) logits = self._logits(self.final_norm(hidden)) return logits, tuple(caches) if use_cache else None def _expected_shapes(model: nn.Module) -> dict[str, tuple[int, ...]]: return {name: tuple(parameter.shape) for name, parameter in model.named_parameters()} def load_model(checkpoint: str | Path, device: str = "cpu") -> FleckModel: """Load the public BF16 artifact strictly, then run it as FP32.""" if device not in {"cpu", "mps"}: raise ValueError("device must be 'cpu' or 'mps'") if device == "mps" and not torch.backends.mps.is_available(): raise RuntimeError("MPS was requested but is not available") source = Path(checkpoint) if not source.is_file(): raise FileNotFoundError(source) model = FleckModel() expected = _expected_shapes(model) with safe_open(str(source), framework="pt", device="cpu") as handle: metadata = handle.metadata() or {} for key, expected_value in MODEL_METADATA.items(): if metadata.get(key) != expected_value: raise ValueError( f"SafeTensors metadata mismatch for {key}: " f"expected {expected_value!r}, got {metadata.get(key)!r}" ) names = set(handle.keys()) if names != set(expected): raise ValueError( "SafeTensors parameter names mismatch: " f"missing={sorted(set(expected) - names)}, extra={sorted(names - set(expected))}" ) for name, shape in expected.items(): tensor_slice = handle.get_slice(name) if tensor_slice.get_dtype() != "BF16": raise ValueError(f"{name}: expected BF16, got {tensor_slice.get_dtype()}") if tuple(tensor_slice.get_shape()) != shape: raise ValueError( f"{name}: expected shape {shape}, got {tuple(tensor_slice.get_shape())}" ) tensors = load_file(str(source), device="cpu") for name, shape in expected.items(): tensor = tensors[name] if tensor.dtype != torch.bfloat16 or tuple(tensor.shape) != shape: raise ValueError(f"{name}: SafeTensors dtype/shape contract mismatch") model.load_state_dict(tensors, strict=True, assign=True) model = model.to(device=torch.device(device), dtype=torch.bfloat16).eval() if any(parameter.dtype != torch.bfloat16 for parameter in model.parameters()): raise TypeError("model parameters must be BF16 after loading") return model def load_tokenizer(path: str | Path) -> Tokenizer: source = Path(path) tokenizer = Tokenizer.from_file(str(source)) if tokenizer.get_vocab_size() != VOCAB_SIZE: raise ValueError(f"tokenizer vocabulary must be {VOCAB_SIZE}") expected = { "": BOS_ID, "": EOS_ID, "": PAD_ID, "": UNK_ID, "<|system|>": SYSTEM_ID, "<|user|>": USER_ID, "<|assistant|>": ASSISTANT_ID, "<|eot|>": EOT_ID, } if set(tokenizer.get_added_tokens_decoder()) != set(expected.values()): raise ValueError("tokenizer added-token set does not match the strict contract") for token, token_id in expected.items(): added_token = tokenizer.get_added_tokens_decoder().get(token_id) if ( tokenizer.token_to_id(token) != token_id or added_token is None or getattr(added_token, "content", None) != token or not bool(getattr(added_token, "special", False)) ): raise ValueError(f"tokenizer special token contract mismatch for {token}") return tokenizer @torch.inference_mode() def generate( model: FleckModel, input_ids: Tensor, max_tokens: int, stop_token_ids: Iterable[int] = (EOS_ID,), ) -> Tensor: if max_tokens < 0: raise ValueError("max_tokens must be non-negative") if input_ids.shape[1] + max_tokens > CONTEXT_LENGTH: raise ValueError(f"prompt plus generation exceeds context_length={CONTEXT_LENGTH}") stop_ids = set(stop_token_ids) generated = input_ids cache: tuple[tuple[Tensor, Tensor], ...] | None = None for _ in range(max_tokens): current = generated if cache is None else generated[:, -1:] logits, cache = model(current, past_key_values=cache, use_cache=True) next_token = logits[:, -1, :].argmax(dim=-1, keepdim=True) generated = torch.cat((generated, next_token), dim=1) if bool(torch.all(torch.isin(next_token, torch.tensor(list(stop_ids), device=next_token.device)))): break return generated def _default_path(name: str) -> str: return str(Path(__file__).with_name(name)) def chat_ids(tokenizer: Tokenizer, user_text: str) -> list[int]: """Encode one training-format user turn and open the assistant turn.""" return [BOS_ID, USER_ID, *tokenizer.encode(user_text, add_special_tokens=False).ids, EOT_ID, ASSISTANT_ID] def decode_reply(tokenizer: Tokenizer, token_ids: list[int]) -> str: """Stop at EOT/EOS and decode only ordinary text.""" for index, token_id in enumerate(token_ids): if token_id in {EOT_ID, EOS_ID}: token_ids = token_ids[:index] break return tokenizer.decode(token_ids, skip_special_tokens=True).strip() def run_single(args: argparse.Namespace, tokenizer: Tokenizer, model: FleckModel) -> None: prompt = args.prompt if args.prompt is not None else "Hello" ids = chat_ids(tokenizer, prompt) input_ids = torch.tensor([ids], dtype=torch.long, device=args.device) output = generate(model, input_ids, args.max_tokens, (EOT_ID, EOS_ID)) reply = decode_reply(tokenizer, output[0, len(ids) :].detach().cpu().tolist()) print(reply) def run_chat(args: argparse.Namespace, tokenizer: Tokenizer, model: FleckModel) -> None: history: list[tuple[str, str]] = [] print("Fleck chat. Type /exit to quit or /clear to reset history.") while True: try: prompt = input("user> ") except (EOFError, KeyboardInterrupt): print() return if prompt.strip() == "/exit": return if prompt.strip() == "/clear": history.clear() print("(history cleared)") continue if not prompt.strip(): continue ids = [BOS_ID] for old_user, old_reply in history: ids.extend([USER_ID, *tokenizer.encode(old_user, add_special_tokens=False).ids, EOT_ID, ASSISTANT_ID]) ids.extend([*tokenizer.encode(old_reply, add_special_tokens=False).ids, EOT_ID]) ids.extend([USER_ID, *tokenizer.encode(prompt, add_special_tokens=False).ids, EOT_ID, ASSISTANT_ID]) if len(ids) + args.max_tokens > CONTEXT_LENGTH: history.clear() ids = chat_ids(tokenizer, prompt) input_ids = torch.tensor([ids], dtype=torch.long, device=args.device) output = generate(model, input_ids, args.max_tokens, (EOT_ID, EOS_ID)) reply = decode_reply(tokenizer, output[0, len(ids) :].detach().cpu().tolist()) print(f"assistant> {reply}") history.append((prompt, reply)) def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--ckpt", default=_default_path("model.safetensors")) parser.add_argument("--tokenizer", default=_default_path("tokenizer.json")) parser.add_argument("--prompt", default=None, help="single prompt; omit for interactive chat") parser.add_argument("--max-tokens", type=int, default=32) parser.add_argument("--device", choices=("cpu", "mps"), default="cpu") parser.add_argument("--no-chat", action="store_true", help="run one prompt instead of interactive chat") return parser.parse_args() def main() -> None: args = parse_args() tokenizer = load_tokenizer(args.tokenizer) model = load_model(args.ckpt, args.device) if args.no_chat or args.prompt is not None: run_single(args, tokenizer, model) else: run_chat(args, tokenizer, model) if __name__ == "__main__": main()