Spaces:
Running on Zero
Running on Zero
| """ | |
| Frox AI Morph 1.1 β Core Language Model | |
| Improvements over Morph 1.0: | |
| - 64K vocab (was 32K) for better multilingual + code coverage | |
| - 16K context window (was 8K) via YaRN RoPE | |
| - QK-norm enabled throughout | |
| - Sliding window (even layers) + full attn (odd layers) interleaved | |
| - Depth-scaled residual init | |
| - Pre-computed causal mask (cached for reuse) | |
| - HF GenerationMixin compatible (used by PEFT, vLLM, TRL) | |
| - save() / from_saved() / from_pretrained() / push_to_hub() helpers | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import math | |
| import os | |
| from dataclasses import dataclass | |
| from pathlib import Path | |
| from typing import Dict, List, Optional, Tuple, Union | |
| import torch | |
| import torch.nn as nn | |
| import torch.nn.functional as F | |
| import torch.utils.checkpoint | |
| from config.model_config import MorphConfig, MorphTextConfig | |
| from model.attention.gqa import MorphDecoderLayer, MorphRMSNorm | |
| # ββ Output types ββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class MorphModelOutput: | |
| last_hidden_state: torch.Tensor | |
| past_key_values: Optional[Tuple] = None | |
| hidden_states: Optional[Tuple] = None | |
| attentions: Optional[Tuple] = None | |
| class MorphCausalLMOutput: | |
| """ | |
| PEFT / HuggingFace Trainer compatible output. | |
| Dict-style access required by PEFT internals. | |
| """ | |
| loss: Optional[torch.Tensor] = None | |
| logits: Optional[torch.Tensor] = None | |
| past_key_values: Optional[Tuple] = None | |
| hidden_states: Optional[Tuple] = None | |
| attentions: Optional[Tuple] = None | |
| def __getitem__(self, key: str): return getattr(self, key) | |
| def __setitem__(self, key: str, v): setattr(self, key, v) | |
| def get(self, key: str, default=None): return getattr(self, key, default) | |
| def __contains__(self, key: str): | |
| return hasattr(self, key) and getattr(self, key) is not None | |
| def keys(self): | |
| return [k for k in ("loss","logits","past_key_values","hidden_states","attentions") | |
| if getattr(self, k, None) is not None] | |
| # ββ Backbone ββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class MorphModel(nn.Module): | |
| """ | |
| Frox Morph 1.1 transformer backbone. | |
| Pure decoder β multimodal tokens injected via projectors. | |
| """ | |
| def __init__(self, config: MorphTextConfig): | |
| super().__init__() | |
| self.config = config | |
| self.padding_idx = config.pad_token_id | |
| # Embedding table covers vocab + reserved special tokens | |
| self.embed_tokens = nn.Embedding( | |
| config.total_vocab_size, | |
| config.hidden_size, | |
| padding_idx=self.padding_idx, | |
| ) | |
| # Transformer layers | |
| self.layers = nn.ModuleList([ | |
| MorphDecoderLayer( | |
| hidden_size=config.hidden_size, | |
| num_heads=config.num_attention_heads, | |
| num_kv_heads=config.num_key_value_heads, | |
| head_dim=config.head_dim, | |
| intermediate_size=config.intermediate_size, | |
| max_position_embeddings=config.max_position_embeddings, | |
| rope_theta=config.rope_theta, | |
| rope_scaling_factor=config.rope_scaling_factor, | |
| rms_norm_eps=config.rms_norm_eps, | |
| layer_idx=i, | |
| qk_norm=config.qk_norm, | |
| use_sliding_window=config.use_sliding_window, | |
| sliding_window_size=config.sliding_window_size, | |
| init_std=config.init_std, | |
| num_hidden_layers=config.num_hidden_layers, | |
| ) | |
| for i in range(config.num_hidden_layers) | |
| ]) | |
| self.norm = MorphRMSNorm(config.hidden_size, eps=config.rms_norm_eps) | |
| self.gradient_checkpointing = False | |
| self._init_embeddings() | |
| def _init_embeddings(self): | |
| """Standard embedding init. Larger init std for larger vocab.""" | |
| std = self.config.init_std | |
| nn.init.normal_(self.embed_tokens.weight, mean=0.0, std=std) | |
| if self.embed_tokens.padding_idx is not None: | |
| self.embed_tokens.weight.data[self.embed_tokens.padding_idx].zero_() | |
| def gradient_checkpointing_enable(self, **kwargs): | |
| self.gradient_checkpointing = True | |
| def gradient_checkpointing_disable(self): | |
| self.gradient_checkpointing = False | |
| def get_input_embeddings(self) -> nn.Embedding: | |
| return self.embed_tokens | |
| def set_input_embeddings(self, value: nn.Embedding): | |
| self.embed_tokens = value | |
| def forward( | |
| self, | |
| input_ids: Optional[torch.LongTensor] = None, | |
| attention_mask: Optional[torch.Tensor] = None, | |
| position_ids: Optional[torch.LongTensor] = None, | |
| past_key_values: Optional[List[Tuple]] = None, | |
| inputs_embeds: Optional[torch.FloatTensor] = None, | |
| use_cache: bool = True, | |
| output_attentions: bool = False, | |
| output_hidden_states: bool = False, | |
| ) -> MorphModelOutput: | |
| if self.gradient_checkpointing and self.training: | |
| use_cache = False # incompatible with grad checkpointing | |
| # Embeddings | |
| if inputs_embeds is None: | |
| inputs_embeds = self.embed_tokens(input_ids) | |
| hidden_states = inputs_embeds | |
| B, S, _ = hidden_states.shape | |
| # Position IDs | |
| if position_ids is None: | |
| past_len = past_key_values[0][0].shape[2] if past_key_values else 0 | |
| position_ids = torch.arange( | |
| past_len, past_len + S, | |
| device=hidden_states.device, | |
| ).unsqueeze(0).expand(B, -1) | |
| # Build causal mask | |
| past_len = past_key_values[0][0].shape[2] if past_key_values else 0 | |
| causal_mask = self._build_causal_mask( | |
| attention_mask, hidden_states.dtype, hidden_states.device, S, past_len | |
| ) | |
| all_hidden_states = () if output_hidden_states else None | |
| all_attentions = () if output_attentions else None | |
| next_cache = () if use_cache else None | |
| for i, layer in enumerate(self.layers): | |
| if output_hidden_states: | |
| all_hidden_states += (hidden_states,) | |
| past_kv = past_key_values[i] if past_key_values is not None else None | |
| if self.gradient_checkpointing and self.training: | |
| def _ckpt_forward(l): | |
| def fn(hs, mask, pos): | |
| return l( | |
| hidden_states=hs, | |
| attention_mask=mask, | |
| position_ids=pos, | |
| past_key_value=None, | |
| use_cache=False, | |
| output_attentions=output_attentions, | |
| ) | |
| return fn | |
| layer_outputs = torch.utils.checkpoint.checkpoint( | |
| _ckpt_forward(layer), | |
| hidden_states, causal_mask, position_ids, | |
| use_reentrant=False, | |
| ) | |
| else: | |
| layer_outputs = layer( | |
| hidden_states=hidden_states, | |
| attention_mask=causal_mask, | |
| position_ids=position_ids, | |
| past_key_value=past_kv, | |
| use_cache=use_cache, | |
| output_attentions=output_attentions, | |
| ) | |
| hidden_states = layer_outputs[0] | |
| if output_attentions: | |
| all_attentions += (layer_outputs[1],) | |
| if use_cache: | |
| next_cache += (layer_outputs[-1],) | |
| hidden_states = self.norm(hidden_states) | |
| if output_hidden_states: | |
| all_hidden_states += (hidden_states,) | |
| return MorphModelOutput( | |
| last_hidden_state=hidden_states, | |
| past_key_values=next_cache, | |
| hidden_states=all_hidden_states, | |
| attentions=all_attentions, | |
| ) | |
| def _build_causal_mask( | |
| self, | |
| attention_mask: Optional[torch.Tensor], | |
| dtype: torch.dtype, | |
| device: torch.device, | |
| seq_len: int, | |
| past_len: int, | |
| ) -> Optional[torch.Tensor]: | |
| """ | |
| 4D causal mask [B_or_1, 1, S_q, S_k]. | |
| Never becomes 5D (was the bug in Morph 1.0). | |
| """ | |
| total_len = seq_len + past_len | |
| min_val = torch.finfo(dtype).min | |
| causal = torch.full( | |
| (seq_len, total_len), fill_value=min_val, | |
| dtype=dtype, device=device, | |
| ) | |
| causal = torch.triu(causal, diagonal=past_len + 1) | |
| causal = causal[None, None, :, :] # [1, 1, S_q, S_k] | |
| if attention_mask is not None: | |
| pad_mask = (1.0 - attention_mask[:, None, None, :].to(dtype)) * min_val | |
| causal = causal + pad_mask # [B, 1, S_q, S_k] | |
| return causal | |
| # ββ Causal LM ββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class MorphForCausalLM(nn.Module): | |
| """ | |
| Frox Morph 1.1 β full causal language model. | |
| HuggingFace / PEFT / TRL compatible: | |
| β prepare_inputs_for_generation() | |
| β can_generate() | |
| β get/set input/output embeddings | |
| β gradient_checkpointing_enable/disable | |
| β forward() accepts return_dict + **kwargs | |
| """ | |
| def __init__(self, config: MorphTextConfig): | |
| super().__init__() | |
| self.config = config | |
| self.model = MorphModel(config) | |
| # LM head: same dim as embedding table | |
| self.lm_head = nn.Linear( | |
| config.hidden_size, config.total_vocab_size, bias=False | |
| ) | |
| # Tie embeddings (saves ~400M params at 64K vocab) | |
| if config.tie_word_embeddings: | |
| self.lm_head.weight = self.model.embed_tokens.weight | |
| # ββ Embedding accessors βββββββββββββββββββββββββββββββββββββββ | |
| def get_input_embeddings(self) -> nn.Embedding: return self.model.embed_tokens | |
| def set_input_embeddings(self, v): self.model.embed_tokens = v | |
| def get_output_embeddings(self) -> nn.Linear: return self.lm_head | |
| def set_output_embeddings(self, v): self.lm_head = v | |
| # ββ Gradient checkpointing ββββββββββββββββββββββββββββββββββββ | |
| def gradient_checkpointing_enable(self, **kwargs): | |
| self.model.gradient_checkpointing_enable() | |
| def gradient_checkpointing_disable(self): | |
| self.model.gradient_checkpointing_disable() | |
| # ββ HF generation compatibility βββββββββββββββββββββββββββββββ | |
| def can_generate(self) -> bool: | |
| return True | |
| def prepare_inputs_for_generation( | |
| self, | |
| input_ids: torch.LongTensor, | |
| past_key_values=None, | |
| attention_mask=None, | |
| inputs_embeds=None, | |
| **kwargs, | |
| ) -> dict: | |
| if past_key_values is not None: | |
| input_ids = input_ids[:, -1:] # only the new token | |
| model_inputs: dict = { | |
| "input_ids": input_ids, | |
| "past_key_values": past_key_values, | |
| "use_cache": kwargs.get("use_cache", True), | |
| "attention_mask": attention_mask, | |
| } | |
| if inputs_embeds is not None and past_key_values is None: | |
| model_inputs.pop("input_ids") | |
| model_inputs["inputs_embeds"] = inputs_embeds | |
| return model_inputs | |
| # ββ Forward βββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def forward( | |
| self, | |
| input_ids: Optional[torch.LongTensor] = None, | |
| attention_mask: Optional[torch.Tensor] = None, | |
| position_ids: Optional[torch.LongTensor] = None, | |
| past_key_values: Optional[List[Tuple]] = None, | |
| inputs_embeds: Optional[torch.FloatTensor] = None, | |
| labels: Optional[torch.LongTensor] = None, | |
| use_cache: bool = True, | |
| output_attentions: bool = False, | |
| output_hidden_states: bool = False, | |
| return_dict: bool = True, # PEFT compatibility | |
| **kwargs, # absorb PEFT extra kwargs | |
| ) -> MorphCausalLMOutput: | |
| outputs = self.model( | |
| input_ids=input_ids, | |
| attention_mask=attention_mask, | |
| position_ids=position_ids, | |
| past_key_values=past_key_values, | |
| inputs_embeds=inputs_embeds, | |
| use_cache=use_cache, | |
| output_attentions=output_attentions, | |
| output_hidden_states=output_hidden_states, | |
| ) | |
| hidden_states = outputs.last_hidden_state | |
| logits = self.lm_head(hidden_states).float() # always float32 | |
| loss = None | |
| if labels is not None: | |
| # Shift for next-token prediction | |
| shift_logits = logits[..., :-1, :].contiguous() | |
| shift_labels = labels[..., 1:].contiguous() | |
| loss = F.cross_entropy( | |
| shift_logits.view(-1, shift_logits.size(-1)), | |
| shift_labels.view(-1), | |
| ignore_index=-100, | |
| ) | |
| return MorphCausalLMOutput( | |
| loss=loss, | |
| logits=logits, | |
| past_key_values=outputs.past_key_values, | |
| hidden_states=outputs.hidden_states, | |
| attentions=outputs.attentions, | |
| ) | |
| # ββ Generation (used in edge / demo / inference engine) ββββββ | |
| def generate( | |
| self, | |
| input_ids: torch.LongTensor, | |
| attention_mask: Optional[torch.Tensor] = None, | |
| max_new_tokens: int = 512, | |
| temperature: float = 0.7, | |
| top_p: float = 0.9, | |
| top_k: int = 50, | |
| repetition_penalty: float = 1.1, | |
| eos_token_id: Optional[int] = None, | |
| pad_token_id: Optional[int] = None, | |
| use_cache: bool = True, | |
| do_sample: bool = True, | |
| stream_callback=None, # NEW 1.1: optional per-token callback | |
| ) -> torch.LongTensor: | |
| eos = eos_token_id if eos_token_id is not None else self.config.eos_token_id | |
| pad = pad_token_id if pad_token_id is not None else self.config.pad_token_id | |
| B = input_ids.shape[0] | |
| generated = input_ids.clone() | |
| past_key_values = None | |
| finished = torch.zeros(B, dtype=torch.bool, device=input_ids.device) | |
| for step in range(max_new_tokens): | |
| curr_input = generated[:, -1:] if past_key_values is not None else generated | |
| out = self.forward( | |
| input_ids=curr_input, | |
| attention_mask=attention_mask, | |
| past_key_values=past_key_values, | |
| use_cache=use_cache, | |
| ) | |
| logits = out.logits[:, -1, :] | |
| past_key_values = out.past_key_values | |
| # Repetition penalty | |
| if repetition_penalty != 1.0: | |
| for b in range(B): | |
| for tid in set(generated[b].tolist()): | |
| if logits[b, tid] < 0: | |
| logits[b, tid] *= repetition_penalty | |
| else: | |
| logits[b, tid] /= repetition_penalty | |
| if temperature != 1.0: | |
| logits = logits / temperature | |
| if top_k > 0: | |
| top_k_vals, _ = torch.topk(logits, min(top_k, logits.size(-1))) | |
| logits[logits < top_k_vals[:, -1:]] = float("-inf") | |
| if do_sample and top_p < 1.0: | |
| sorted_logits, sorted_idx = torch.sort(logits, descending=True) | |
| cum_probs = torch.cumsum(F.softmax(sorted_logits, dim=-1), dim=-1) | |
| remove = cum_probs - F.softmax(sorted_logits, dim=-1) > top_p | |
| sorted_logits[remove] = float("-inf") | |
| logits = torch.zeros_like(logits).scatter_(1, sorted_idx, sorted_logits) | |
| if do_sample: | |
| probs = F.softmax(logits, dim=-1) | |
| next_token = torch.multinomial(probs, num_samples=1) | |
| else: | |
| next_token = logits.argmax(dim=-1, keepdim=True) | |
| next_token = torch.where( | |
| finished.unsqueeze(-1), | |
| torch.full_like(next_token, pad), | |
| next_token, | |
| ) | |
| generated = torch.cat([generated, next_token], dim=-1) | |
| if attention_mask is not None: | |
| attention_mask = torch.cat([ | |
| attention_mask, | |
| torch.ones(B, 1, device=attention_mask.device), | |
| ], dim=-1) | |
| finished = finished | (next_token.squeeze(-1) == eos) | |
| # NEW 1.1: stream callback for real-time output | |
| if stream_callback is not None: | |
| for b in range(B): | |
| if not finished[b]: | |
| stream_callback(b, next_token[b, 0].item(), step) | |
| if finished.all(): | |
| break | |
| return generated | |
| # ββ Utilities βββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def param_count(self) -> dict: | |
| total = sum(p.numel() for p in self.parameters()) | |
| trainable = sum(p.numel() for p in self.parameters() if p.requires_grad) | |
| return { | |
| "total": total, | |
| "trainable": trainable, | |
| "total_billions": round(total / 1e9, 3), | |
| "trainable_billions": round(trainable / 1e9, 3), | |
| } | |
| def save(self, path: str): | |
| from dataclasses import asdict | |
| p = Path(path) | |
| p.mkdir(parents=True, exist_ok=True) | |
| torch.save(self.state_dict(), p / "model.pt") | |
| with open(p / "config.json", "w") as f: | |
| json.dump(asdict(self.config), f, indent=2) | |
| print(f"β Morph 1.1 LM saved to {path}") | |
| def from_saved(cls, path: str, device: str = "cpu") -> "MorphForCausalLM": | |
| from utils.common import require_checkpoint_dir | |
| p = require_checkpoint_dir(path) | |
| with open(p / "config.json") as f: | |
| cfg_dict = json.load(f) | |
| config = MorphTextConfig(**cfg_dict) | |
| model = cls(config) | |
| state = torch.load(p / "model.pt", map_location=device, weights_only=True) | |
| model.load_state_dict(state, strict=False) | |
| return model | |
| def from_config(cls, config: MorphTextConfig) -> "MorphForCausalLM": | |
| """Create model with random weights from config.""" | |
| return cls(config) | |
| def from_morph_1_checkpoint(cls, path: str) -> "MorphForCausalLM": | |
| """ | |
| Load a Morph 1.0 checkpoint into a Morph 1.1 model. | |
| Handles vocab size mismatch (32K β 64K) by zero-padding embeddings. | |
| """ | |
| p = Path(path) | |
| with open(p / "config.json") as f: | |
| old_cfg = json.load(f) | |
| # Build 1.1 config with same architecture but upgraded vocab | |
| new_cfg = MorphTextConfig(**old_cfg) | |
| new_cfg.vocab_size = 64000 | |
| new_cfg.qk_norm = True | |
| new_cfg.version = "1.1.0" | |
| model = cls(new_cfg) | |
| # Load old weights, skip mismatched embed table | |
| old_state = torch.load(p / "model.pt", map_location="cpu", weights_only=True) | |
| new_state = model.state_dict() | |
| for name, param in old_state.items(): | |
| if name not in new_state: | |
| continue | |
| if param.shape == new_state[name].shape: | |
| new_state[name] = param | |
| elif "embed_tokens" in name or "lm_head" in name: | |
| # Pad vocabulary dimension: copy old rows, leave new rows at init | |
| old_rows = param.shape[0] | |
| new_state[name][:old_rows] = param | |
| print(f" Padded {name}: {param.shape} β {new_state[name].shape}") | |
| model.load_state_dict(new_state) | |
| print(f"β Morph 1.1 loaded from Morph 1.0 checkpoint: {path}") | |
| return model | |