Feature Extraction
Transformers
Safetensors
microglot
biology
genomics
dna
microbiology
mixture-of-experts
species-conditioning
custom_code
Instructions to use athanzli/MicroGlot with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use athanzli/MicroGlot with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("feature-extraction", model="athanzli/MicroGlot", trust_remote_code=True)# pip install -U transformers accelerate # Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("athanzli/MicroGlot", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
Download modeling_microglot.py from athanzli/MicroGlot: direct link, hf CLI and curl.
- Browser
- Download file 82.1 kB
-
https://huggingface.co/athanzli/MicroGlot/resolve/main/modeling_microglot.py
- Command line
-
hf download hf://athanzli/MicroGlot/modeling_microglot.py
-
curl -L -o modeling_microglot.py https://huggingface.co/athanzli/MicroGlot/resolve/main/modeling_microglot.py
82.1 kB
| """MicroGlot: a taxonomy-informed sparse (mixture-of-experts) decoder-only DNA language model. | |
| One code file serves the three models of the repository ``athanzli/MicroGlot``: | |
| * the repository root - MicroGlot, the species-conditioned language model (``MicroGlotForCausalLM``) | |
| * ``plain/`` - MicroGlot-plain, the same architecture without species conditioning | |
| * ``species_encoder/`` - the Species-encoder, which predicts a species embedding from DNA (``MicroGlotSpeciesEncoder``) | |
| Species inputs follow the ``input_ids`` / ``inputs_embeds`` convention: | |
| * ``species_ids`` (LongTensor ``[batch]``): rows of the model's table of the 99,700 pretraining species | |
| (produced by the tokenizer's ``species=`` argument); ``-1`` marks a sequence whose | |
| species is unknown and must be inferred by an attached species encoder. | |
| * ``species_embeds`` (FloatTensor ``[batch, 32]`` or ``[32]``): your own 32-d species vectors. | |
| MicroGlot runs on NVIDIA GPUs with flash-attn installed, as it was trained and benchmarked. The numerical | |
| code (attention, mixture of experts, decoder layers) is the code MicroGlot was trained with, unchanged. | |
| """ | |
| import math | |
| import warnings | |
| from dataclasses import dataclass | |
| from typing import List, Optional, Tuple, Union | |
| import os as _os | |
| _os.environ.setdefault("TF_CPP_MIN_LOG_LEVEL", "3") | |
| import torch | |
| import torch.nn as nn | |
| import torch.nn.functional as F | |
| import transformers | |
| from packaging import version | |
| from torch.utils.checkpoint import checkpoint | |
| # Supported versions (see the model card): PyTorch >= 2.7, transformers >= 4.51.3, flash-attn 2.7.4-2.8.3 tested. | |
| # PyTorch 2.5 and 2.6 compute nn.RMSNorm(eps=None) of bf16 inputs with the bf16 epsilon, which changes the outputs. | |
| _INSTALL = "https://huggingface.co/athanzli/MicroGlot#installation" | |
| if version.parse(torch.__version__).release < (2, 7): | |
| raise ImportError(f"MicroGlot requires PyTorch 2.7 or later (found {torch.__version__}). See {_INSTALL}.") | |
| if version.parse(transformers.__version__).release < (4, 51, 3): | |
| raise ImportError(f"MicroGlot requires transformers 4.51.3 or later (found {transformers.__version__}). " | |
| f"See {_INSTALL}.") | |
| # MicroGlot was trained and benchmarked with flash-attn's rotary position-embedding kernel, so it | |
| # is required: every installation then computes the same embeddings. | |
| try: | |
| from flash_attn.layers.rotary import RotaryEmbedding | |
| except ImportError as _flash_attn_error: | |
| raise ImportError( | |
| "MicroGlot requires FlashAttention-2 (the flash-attn package, whose rotary position-embedding kernel was " | |
| f"used to train and benchmark the model) and an NVIDIA GPU. Install it as described at {_INSTALL}. " | |
| f"(Importing flash_attn failed with: {_flash_attn_error})" | |
| ) from _flash_attn_error | |
| from transformers import PretrainedConfig, PreTrainedModel | |
| from transformers.modeling_outputs import ModelOutput | |
| from transformers.utils import logging | |
| logger = logging.get_logger(__name__) | |
| # from_pretrained(torch_dtype=...) is called dtype= from transformers 4.56 on | |
| _DTYPE_KWARG = "dtype" if version.parse(transformers.__version__).release >= (4, 56) else "torch_dtype" | |
| class MicroGlotModelOutput(ModelOutput): | |
| """Backbone output. The species position is removed, so every tensor lines up with ``input_ids``. | |
| last_hidden_state: ``[batch, length, hidden]``, the final-norm output. | |
| hidden_states: with ``output_hidden_states=True``, ``num_hidden_layers + 1`` tensors (24): ``[0]`` the | |
| token embeddings, ``[k]`` the output of decoder layer k (1..22), ``[-1]`` the final-norm output | |
| (equal to ``last_hidden_state``). | |
| moe_loss: the router load-balancing loss summed over layers (already scaled by | |
| ``router_aux_loss_coef``), or None with ``output_router_logits=False``. | |
| species_embeds: ``[batch, 32]`` unit-norm species vectors actually used, in the model dtype | |
| (None for MicroGlot-plain). | |
| """ | |
| last_hidden_state: torch.FloatTensor = None | |
| hidden_states: Optional[Tuple[torch.FloatTensor, ...]] = None | |
| moe_loss: Optional[torch.FloatTensor] = None | |
| species_embeds: Optional[torch.FloatTensor] = None | |
| class MicroGlotCausalLMOutput(ModelOutput): | |
| """Next-token output of ``MicroGlotForCausalLM``. | |
| loss: with ``labels``, the mean next-token cross-entropy (plus ``moe_loss`` with ``output_router_logits=True``). | |
| logits: ``[batch, length, vocab]`` next-token logits. | |
| hidden_states: as in ``MicroGlotModelOutput`` (with ``skip_lm_head=True``, the final hidden state only). | |
| species_embeds: ``[batch, 32]`` unit-norm species vectors actually used (None for MicroGlot-plain). | |
| moe_loss: with ``output_router_logits=True``, the router load-balancing loss (as in ``MicroGlotModelOutput``). | |
| """ | |
| loss: Optional[torch.FloatTensor] = None | |
| logits: Optional[torch.FloatTensor] = None | |
| hidden_states: Optional[Tuple[torch.FloatTensor, ...]] = None | |
| species_embeds: Optional[torch.FloatTensor] = None | |
| moe_loss: Optional[torch.FloatTensor] = None | |
| class MicroGlotSpeciesEncoderOutput(ModelOutput): | |
| """Species-encoder output, in the style of ``CLIPTextModelOutput``. | |
| species_embeds: ``[batch, 32]`` unit-norm predicted species vectors, normalised in the model dtype: the | |
| vectors MicroGlot is conditioned on (listed first, so pipelines return it). | |
| embeddings: ``[batch, 32]`` the projection output before normalisation. | |
| pooler_output: ``[batch, hidden]`` the pooled state (last real token). | |
| last_hidden_state, hidden_states, moe_loss: as in ``MicroGlotModelOutput``. | |
| """ | |
| species_embeds: torch.FloatTensor = None | |
| embeddings: Optional[torch.FloatTensor] = None | |
| pooler_output: Optional[torch.FloatTensor] = None | |
| last_hidden_state: Optional[torch.FloatTensor] = None | |
| hidden_states: Optional[Tuple[torch.FloatTensor, ...]] = None | |
| moe_loss: Optional[torch.FloatTensor] = None | |
| class MoEParameterCounts: | |
| """Parameter counts for MoE models.""" | |
| total_params: int | |
| activated_params: int | |
| non_embedding_total: int | |
| non_embedding_activated: int | |
| embedding_params: int | |
| moe_total_params: int | |
| moe_activated_params: int | |
| dense_params: int | |
| num_moe_layers: int | |
| num_dense_layers: int | |
| experts_per_layer: List[int] | |
| top_k: int | |
| def __str__(self) -> str: | |
| """Human-readable summary string.""" | |
| return ( | |
| f"MoE Parameter Counts:\n" | |
| f" Total Parameters: {self.total_params:,} ({self.total_params / 1e9:.2f}B)\n" | |
| f" Activated Parameters: {self.activated_params:,} ({self.activated_params / 1e6:.1f}M)\n" | |
| f" Non-Embedding Total: {self.non_embedding_total:,} ({self.non_embedding_total / 1e9:.2f}B)\n" | |
| f" Non-Embedding Activated: {self.non_embedding_activated:,} ({self.non_embedding_activated / 1e6:.1f}M)\n" | |
| f" Embedding Parameters: {self.embedding_params:,} ({self.embedding_params / 1e6:.1f}M)\n" | |
| f" MoE Layers: {self.num_moe_layers} (total: {self.moe_total_params:,}, activated: {self.moe_activated_params:,})\n" | |
| f" Dense Layers: {self.num_dense_layers} ({self.dense_params:,})\n" | |
| f" Top-K Routing: {self.top_k}\n" | |
| f" Experts per Layer: {self.experts_per_layer}" | |
| ) | |
| def to_dict(self) -> dict: | |
| """Convert to dictionary for JSON serialization.""" | |
| return { | |
| "total_params": self.total_params, | |
| "activated_params": self.activated_params, | |
| "non_embedding_total": self.non_embedding_total, | |
| "non_embedding_activated": self.non_embedding_activated, | |
| "embedding_params": self.embedding_params, | |
| "moe_total_params": self.moe_total_params, | |
| "moe_activated_params": self.moe_activated_params, | |
| "dense_params": self.dense_params, | |
| "num_moe_layers": self.num_moe_layers, | |
| "num_dense_layers": self.num_dense_layers, | |
| "experts_per_layer": self.experts_per_layer, | |
| "top_k": self.top_k, | |
| } | |
| def compute_moe_parameter_counts(config: "GenomicModelConfig") -> MoEParameterCounts: | |
| """Compute total and activated parameter counts for MoE models from config.""" | |
| hidden_size = config.hidden_size | |
| intermediate_size = config.intermediate_size | |
| vocab_size = config.vocab_size | |
| num_layers = config.num_hidden_layers | |
| num_query_heads = config.num_query_heads | |
| num_kv_heads = config.num_kv_heads | |
| head_dim = config.head_dim | |
| top_k = config.num_experts_per_tok if config.use_moe else 0 | |
| use_residual = config.moe_use_residual if config.use_moe else False | |
| if config.use_moe and config.moe_layer_experts is not None: | |
| experts_per_layer = config.moe_layer_experts | |
| else: | |
| experts_per_layer = [0] * num_layers | |
| embedding_params = vocab_size * hidden_size | |
| attn_params = ( | |
| hidden_size * (num_query_heads * head_dim) + | |
| hidden_size * (num_kv_heads * head_dim) + | |
| hidden_size * (num_kv_heads * head_dim) + | |
| (num_query_heads * head_dim) * hidden_size | |
| ) | |
| layernorm_params = 2 * hidden_size | |
| ffn_params = ( | |
| hidden_size * (2 * intermediate_size) + | |
| intermediate_size * hidden_size | |
| ) | |
| moe_total_params = 0 | |
| moe_activated_params = 0 | |
| dense_params = 0 | |
| num_moe_layers = 0 | |
| num_dense_layers = 0 | |
| for layer_idx, num_experts in enumerate(experts_per_layer): | |
| layer_base_params = attn_params + layernorm_params | |
| if num_experts > 0: | |
| num_moe_layers += 1 | |
| expert_total = num_experts * ffn_params | |
| gate_params = hidden_size * num_experts | |
| expert_activated = (top_k / num_experts) * expert_total + gate_params | |
| species_film_params = 0 | |
| if config.species_emb_dim is not None: | |
| species_film_params = 2 * (config.species_emb_dim * num_experts + num_experts) | |
| residual_params = (ffn_params + hidden_size * 2) if use_residual else 0 | |
| layer_moe_total = expert_total + gate_params + species_film_params + residual_params | |
| layer_moe_activated = expert_activated + species_film_params + residual_params | |
| moe_total_params += layer_base_params + layer_moe_total | |
| moe_activated_params += layer_base_params + layer_moe_activated | |
| else: | |
| num_dense_layers += 1 | |
| layer_total = layer_base_params + ffn_params | |
| dense_params += layer_total | |
| final_norm_params = hidden_size | |
| species_encoder_params = 0 | |
| if config.use_species_encoder and config.species_emb_dim is not None: | |
| species_encoder_params = config.species_emb_dim | |
| non_embedding_total = moe_total_params + dense_params + final_norm_params + species_encoder_params | |
| non_embedding_activated = moe_activated_params + dense_params + final_norm_params + species_encoder_params | |
| total_params = embedding_params + non_embedding_total | |
| activated_params = embedding_params + non_embedding_activated | |
| return MoEParameterCounts( | |
| total_params=total_params, | |
| activated_params=activated_params, | |
| non_embedding_total=non_embedding_total, | |
| non_embedding_activated=non_embedding_activated, | |
| embedding_params=embedding_params, | |
| moe_total_params=moe_total_params, | |
| moe_activated_params=moe_activated_params, | |
| dense_params=dense_params, | |
| num_moe_layers=num_moe_layers, | |
| num_dense_layers=num_dense_layers, | |
| experts_per_layer=experts_per_layer, | |
| top_k=top_k, | |
| ) | |
| def count_parameters(model: nn.Module, requires_grad_only: bool = False) -> int: | |
| """Count the number of parameters in a model.""" | |
| if requires_grad_only: | |
| return sum(p.numel() for p in model.parameters() if p.requires_grad) | |
| return sum(p.numel() for p in model.parameters()) | |
| def get_model_parameter_counts(model: "GenomicModel") -> MoEParameterCounts: | |
| """Get parameter counts from an instantiated model.""" | |
| if hasattr(model, 'model'): | |
| base_model = model.model | |
| config = model.config | |
| else: | |
| base_model = model | |
| config = model.config | |
| counts = compute_moe_parameter_counts(config) | |
| actual_total = count_parameters(model) | |
| expected_diff = 0 | |
| if hasattr(model, 'lm_head'): | |
| expected_diff = config.vocab_size * config.hidden_size | |
| if abs(actual_total - counts.total_params - expected_diff) > 1000: | |
| import warnings | |
| warnings.warn( | |
| f"Parameter count mismatch: analytical={counts.total_params:,}, " | |
| f"actual={actual_total:,}, expected_diff={expected_diff:,}. " | |
| f"Difference: {actual_total - counts.total_params - expected_diff:,}" | |
| ) | |
| return counts | |
| def format_parameter_count(params: int) -> str: | |
| """Format parameter count in human-readable form.""" | |
| if params >= 1e9: | |
| return f"{params / 1e9:.2f}B" | |
| elif params >= 1e6: | |
| return f"{params / 1e6:.1f}M" | |
| elif params >= 1e3: | |
| return f"{params / 1e3:.1f}K" | |
| else: | |
| return str(params) | |
| def print_model_parameter_summary(config: "GenomicModelConfig") -> None: | |
| """Print a detailed parameter summary for a model configuration.""" | |
| counts = compute_moe_parameter_counts(config) | |
| print("=" * 60) | |
| print("Model Parameter Summary") | |
| print("=" * 60) | |
| print(f"Total Parameters: {format_parameter_count(counts.total_params):>10} ({counts.total_params:,})") | |
| print(f"Activated Parameters: {format_parameter_count(counts.activated_params):>10} ({counts.activated_params:,})") | |
| print("-" * 60) | |
| print(f"Embedding Parameters: {format_parameter_count(counts.embedding_params):>10}") | |
| print(f"Non-Embedding Total: {format_parameter_count(counts.non_embedding_total):>10}") | |
| print(f"Non-Embedding Active: {format_parameter_count(counts.non_embedding_activated):>10}") | |
| print("-" * 60) | |
| print(f"Dense Layers: {counts.num_dense_layers:>10} ({format_parameter_count(counts.dense_params)})") | |
| print(f"MoE Layers: {counts.num_moe_layers:>10} (total: {format_parameter_count(counts.moe_total_params)}, " | |
| f"active: {format_parameter_count(counts.moe_activated_params)})") | |
| print(f"Top-K Routing: {counts.top_k:>10}") | |
| print(f"Experts per Layer: {counts.experts_per_layer}") | |
| print("=" * 60) | |
| class MicroGlotConfig(PretrainedConfig): | |
| """Configuration shared by MicroGlot, MicroGlot-plain and the Species-encoder. | |
| The architecture fields (vocab_size, hidden_size, num_hidden_layers, num_query_heads, num_kv_heads, | |
| intermediate_size, rope_theta, moe_layer_experts, num_experts_per_tok, moe_use_residual, | |
| gate_softmax_over_all_experts, router_aux_loss_coef, moe_dispatch, ...) are those of the pretrained models. | |
| `moe_dispatch`: "sorted" (default), "loop" or "grouped". | |
| Species-related fields: | |
| species_emb_dim (`int`, *optional*): size of the species vector (32). `None` -> no species input | |
| (MicroGlot-plain, and the backbone of the species encoder). | |
| prepend_species_token (`bool`): insert the projected species vector as a token after `[BOS]`. | |
| num_species (`int`): rows in the built-in species table addressed by `species_ids` (99,700; 0 = none). | |
| species_vocab_sha256 (`str`, *optional*): sha256 of the species names in table order, joined by | |
| newlines; the tokenizer's `species_vocab_sha256` must match it. | |
| species_encoder_repo / species_encoder_revision / species_encoder_subfolder (`str`, *optional*): where | |
| `load_species_encoder()` and `from_pretrained(..., species_encoder=True)` find the Species-encoder. | |
| `species_encoder_repo=None`: the repository or folder this model was loaded from, at the same | |
| revision; `species_encoder_subfolder` is the encoder's folder there (`"species_encoder"`). | |
| species_encoder_embedding_dim (`int`): output size of `MicroGlotSpeciesEncoder` (32). | |
| species_encoder_pooling_strategy (`str`): `"eos"` (last real token) or `"mean"`. | |
| """ | |
| model_type = "microglot" | |
| keys_to_ignore_at_inference = ["moe_loss", "species_embeds"] | |
| def __init__( | |
| self, | |
| vocab_size: int = 8192, | |
| hidden_size: int = 1024, | |
| num_hidden_layers: int = 23, | |
| num_query_heads: int = 16, | |
| num_kv_heads: int = 8, | |
| intermediate_size: Optional[int] = None, | |
| attention_dropout_prob: float = 0.0, | |
| classifier_dropout_prob: float = 0.0, | |
| attn_output_dropout_prob: float = 0.0, | |
| moe_output_dropout_prob: float = 0.0, | |
| ffn_dropout_prob: float = 0.0, | |
| moe_expert_dropout_prob: float = 0.0, | |
| rope_theta: float = 500000.0, | |
| max_trained_length: int = 8192, | |
| init_scale: float = 0.1, | |
| use_swiglu: bool = True, | |
| gradient_checkpointing: bool = False, | |
| use_flash_attention: bool = True, | |
| pad_token_id: int = 0, | |
| bos_token_id: int = 1, | |
| eos_token_id: int = 2, | |
| num_experts_per_tok: int = 1, | |
| router_aux_loss_coef: float = 0.001, | |
| moe_layer_experts: Optional[List[int]] = None, | |
| moe_use_residual: bool = True, | |
| moe_capacity_factor: float = 1.25, | |
| moe_eval_capacity_factor: float = 2.0, | |
| moe_min_capacity: int = 4, | |
| moe_noisy_gate_policy: Optional[str] = None, | |
| moe_drop_tokens: bool = True, | |
| moe_use_rts: bool = True, | |
| gate_softmax_over_all_experts: bool = True, | |
| moe_dispatch: str = "sorted", | |
| species_emb_dim: Optional[int] = None, | |
| prepend_species_token: bool = False, | |
| num_species: int = 0, | |
| species_encoder_embedding_dim: int = 32, | |
| species_encoder_pooling_strategy: str = "eos", | |
| species_encoder_repo: Optional[str] = None, | |
| species_encoder_revision: Optional[str] = None, | |
| species_encoder_subfolder: Optional[str] = None, | |
| species_vocab_sha256: Optional[str] = None, | |
| use_species_encoder: bool = False, | |
| **kwargs, | |
| ): | |
| if use_species_encoder: # a training-code config: the species encoder is bundled inside the checkpoint | |
| logger.warning_once( | |
| "This checkpoint bundles a copy of the species encoder, which this code ignores. Species names " | |
| "and vectors work unchanged. To infer species, attach the Species-encoder with " | |
| "model.load_species_encoder()." | |
| ) | |
| if species_encoder_repo is None: | |
| species_encoder_repo, species_encoder_subfolder = "athanzli/MicroGlot", "species_encoder" | |
| super().__init__(pad_token_id=pad_token_id, eos_token_id=eos_token_id, bos_token_id=bos_token_id, **kwargs) | |
| self.vocab_size = vocab_size | |
| self.hidden_size = hidden_size | |
| self.num_hidden_layers = num_hidden_layers | |
| self.num_query_heads = num_query_heads | |
| self.num_kv_heads = num_kv_heads | |
| if intermediate_size is None: | |
| if use_swiglu: | |
| hidden_dim = (8 * hidden_size) // 3 | |
| intermediate_size = 256 * ((hidden_dim + 255) // 256) | |
| else: | |
| intermediate_size = 4 * hidden_size | |
| self.intermediate_size = intermediate_size | |
| self.attention_dropout_prob = attention_dropout_prob | |
| self.classifier_dropout_prob = classifier_dropout_prob | |
| self.attn_output_dropout_prob = attn_output_dropout_prob | |
| self.moe_output_dropout_prob = moe_output_dropout_prob | |
| self.ffn_dropout_prob = ffn_dropout_prob | |
| self.moe_expert_dropout_prob = moe_expert_dropout_prob | |
| self.rope_theta = rope_theta | |
| self.max_trained_length = max_trained_length | |
| self.init_scale = init_scale | |
| self.use_swiglu = use_swiglu | |
| self.gradient_checkpointing = gradient_checkpointing | |
| self.use_flash_attention = use_flash_attention | |
| self.num_experts_per_tok = num_experts_per_tok | |
| self.router_aux_loss_coef = router_aux_loss_coef | |
| self.moe_layer_experts = moe_layer_experts | |
| self.use_moe = moe_layer_experts is not None and any(n > 0 for n in moe_layer_experts) | |
| self.moe_use_residual = moe_use_residual | |
| self.moe_capacity_factor = moe_capacity_factor | |
| self.moe_eval_capacity_factor = moe_eval_capacity_factor | |
| self.moe_min_capacity = moe_min_capacity | |
| self.moe_noisy_gate_policy = moe_noisy_gate_policy | |
| self.moe_drop_tokens = moe_drop_tokens | |
| self.moe_use_rts = moe_use_rts | |
| self.gate_softmax_over_all_experts = gate_softmax_over_all_experts | |
| self.moe_dispatch = moe_dispatch | |
| self.species_emb_dim = species_emb_dim | |
| self.use_species_encoder = False # a training-code flag; the encoder is attached separately | |
| self.prepend_species_token = prepend_species_token | |
| self.num_species = num_species | |
| self.species_encoder_embedding_dim = species_encoder_embedding_dim | |
| self.species_encoder_pooling_strategy = species_encoder_pooling_strategy | |
| self.species_encoder_repo = species_encoder_repo | |
| self.species_encoder_revision = species_encoder_revision | |
| self.species_encoder_subfolder = species_encoder_subfolder | |
| self.species_vocab_sha256 = species_vocab_sha256 | |
| if prepend_species_token and species_emb_dim is None: | |
| raise ValueError("species_emb_dim must be set when prepend_species_token=True.") | |
| if num_species and species_emb_dim is None: | |
| raise ValueError("num_species > 0 needs species_emb_dim (the width of the species table).") | |
| assert hidden_size % num_query_heads == 0 | |
| self.head_dim = hidden_size // num_query_heads | |
| assert num_query_heads % num_kv_heads == 0, "num_query_heads must be divisible by num_kv_heads" | |
| self.num_kv_groups = num_query_heads // num_kv_heads | |
| self._validate_moe_config() | |
| def _validate_moe_config(self): | |
| if not self.use_moe: | |
| return | |
| if len(self.moe_layer_experts) != self.num_hidden_layers: | |
| raise ValueError("moe_layer_experts must have one entry per layer (0 = dense).") | |
| for i, n in enumerate(self.moe_layer_experts): | |
| if not isinstance(n, int) or n < 0: | |
| raise ValueError(f"moe_layer_experts[{i}] = {n} is invalid.") | |
| if 0 < n < self.num_experts_per_tok: | |
| raise ValueError(f"moe_layer_experts[{i}] = {n} < num_experts_per_tok.") | |
| def get_num_experts_for_layer(self, layer_idx: int) -> int: | |
| """Number of experts in layer `layer_idx` (0 = dense feed-forward).""" | |
| if not self.use_moe: | |
| return 0 | |
| return self.moe_layer_experts[layer_idx] | |
| def get_parameter_counts(self) -> "MoEParameterCounts": | |
| """Total and activated parameter counts for this configuration.""" | |
| return compute_moe_parameter_counts(self) | |
| def print_parameter_summary(self) -> None: | |
| """Print a detailed parameter summary for this configuration.""" | |
| print_model_parameter_summary(self) | |
| def to_dict(self): | |
| # save_pretrained() copies this code file next to config.json (see register_for_auto_class below), so | |
| # keep every auto_map entry local: a saved model then runs the code it was saved with, not the Hub's | |
| # latest (transformers rewrites Hub-loaded entries to "athanzli/MicroGlot--modeling_microglot..."). | |
| out = super().to_dict() | |
| if isinstance(out.get("auto_map"), dict): | |
| out["auto_map"] = {k: v.split("--")[-1] if isinstance(v, str) else v for k, v in out["auto_map"].items()} | |
| return out | |
| def get_config_dict(cls, *args, **kwargs): | |
| config_dict, kwargs = super().get_config_dict(*args, **kwargs) | |
| if config_dict.get("model_type") == "genomic_lm": # the training code's name of this model type | |
| config_dict["model_type"] = cls.model_type | |
| return config_dict, kwargs | |
| GenomicModelConfig = MicroGlotConfig # the training code's name, used in the layer code's type hints | |
| class MicroGlotSpeciesEncoderConfig(MicroGlotConfig): | |
| """Configuration of the Species-encoder: `MicroGlotConfig` under its own model type, so that the three | |
| models of the repository, which share this code file, keep distinct Auto classes.""" | |
| model_type = "microglot_species_encoder" | |
| def __init__(self, **kwargs): # explicit: transformers 5 would otherwise turn the subclass into a dataclass | |
| super().__init__(**kwargs) | |
| def get_config_dict(cls, *args, **kwargs): | |
| config_dict, kwargs = super().get_config_dict(*args, **kwargs) | |
| if config_dict.get("model_type") in ("microglot", "genomic_lm"): # encoder folders of the training code | |
| config_dict["model_type"] = cls.model_type | |
| return config_dict, kwargs | |
| class GenomicAttention(nn.Module): | |
| """Grouped Query Attention (GQA) with RoPE and Flash Attention 2.0.""" | |
| def __init__(self, config: GenomicModelConfig): | |
| super().__init__() | |
| self.config = config | |
| self.hidden_size = config.hidden_size | |
| self.num_query_heads = config.num_query_heads | |
| self.num_kv_heads = config.num_kv_heads | |
| self.num_kv_groups = config.num_kv_groups | |
| self.head_dim = config.head_dim | |
| self.attention_dropout = config.attention_dropout_prob | |
| self.q_proj = nn.Linear(self.hidden_size, self.num_query_heads * self.head_dim, bias=False) | |
| self.k_proj = nn.Linear(self.hidden_size, self.num_kv_heads * self.head_dim, bias=False) | |
| self.v_proj = nn.Linear(self.hidden_size, self.num_kv_heads * self.head_dim, bias=False) | |
| self.o_proj = nn.Linear(self.num_query_heads * self.head_dim, self.hidden_size, bias=False) | |
| self.attn_output_dropout_prob = config.attn_output_dropout_prob | |
| self.attn_output_dropout = nn.Dropout(config.attn_output_dropout_prob) if config.attn_output_dropout_prob > 0 else nn.Identity() | |
| self.rotary_emb = RotaryEmbedding( | |
| dim=self.head_dim, | |
| base=config.rope_theta, | |
| interleaved=False | |
| ) | |
| def forward( | |
| self, | |
| hidden_states: torch.Tensor, | |
| attention_mask: Optional[torch.Tensor] = None, | |
| ) -> torch.Tensor: | |
| """Apply causal (autoregressive) Flash Attention with RoPE.""" | |
| batch_size, seq_len, _ = hidden_states.shape | |
| if attention_mask is None: | |
| attention_mask = torch.ones((batch_size, seq_len), device=hidden_states.device, dtype=torch.bool) | |
| else: | |
| attention_mask = attention_mask.to(dtype=torch.bool) | |
| query_states = self.q_proj(hidden_states).view(batch_size, seq_len, self.num_query_heads, self.head_dim).contiguous() | |
| key_states = self.k_proj(hidden_states).view(batch_size, seq_len, self.num_kv_heads, self.head_dim).contiguous() | |
| value_states = self.v_proj(hidden_states).view(batch_size, seq_len, self.num_kv_heads, self.head_dim).contiguous() | |
| qkv_states = self.rotary_emb( | |
| qkv = torch.cat([query_states, key_states, value_states], dim=2), | |
| num_heads_q = self.num_query_heads, | |
| ) | |
| query_states = qkv_states[:, :, :self.num_query_heads, :].contiguous() | |
| key_states = qkv_states[:, :, self.num_query_heads:self.num_query_heads + self.num_kv_heads, :].contiguous() | |
| value_states = qkv_states[:, :, self.num_query_heads + self.num_kv_heads:, :].contiguous() | |
| query_states = query_states.transpose(1, 2) | |
| key_states = key_states.transpose(1, 2) | |
| value_states = value_states.transpose(1, 2) | |
| # SDPA picks the fastest kernel the GPU and dtype support: flash attention for bf16/fp16 on | |
| # Ampere or newer, another kernel otherwise (forcing flash left fp32 and older GPUs without one). | |
| attn_output = F.scaled_dot_product_attention( | |
| query_states, | |
| key_states, | |
| value_states, | |
| dropout_p=self.attention_dropout if self.training else 0.0, | |
| is_causal=True, | |
| enable_gqa=True, | |
| ) | |
| attn_output = attn_output.transpose(1, 2) | |
| attn_output = attn_output.reshape(batch_size, seq_len, self.num_query_heads * self.head_dim) | |
| attn_output = self.o_proj(attn_output) | |
| attn_output = self.attn_output_dropout(attn_output) | |
| return attn_output | |
| class SwiGLU(nn.Module): | |
| """SwiGLU activation function.""" | |
| def __init__(self, config: GenomicModelConfig, dropout_prob: float = None): | |
| super().__init__() | |
| self.hidden_size = config.hidden_size | |
| self.intermediate_size = config.intermediate_size | |
| self.gate_up_proj = nn.Linear(self.hidden_size, 2 * self.intermediate_size, bias=False) | |
| self.down_proj = nn.Linear(self.intermediate_size, self.hidden_size, bias=False) | |
| self.dropout_prob = dropout_prob if dropout_prob is not None else getattr(config, 'ffn_dropout_prob', 0.0) | |
| self.dropout = nn.Dropout(self.dropout_prob) if self.dropout_prob > 0 else nn.Identity() | |
| def forward(self, x: torch.Tensor) -> torch.Tensor: | |
| gate_up = self.gate_up_proj(x) | |
| gate, up = gate_up.chunk(2, dim=-1) | |
| out = self.down_proj(F.silu(gate) * up) | |
| return self.dropout(out) | |
| class TopKGate(nn.Module): | |
| """Top-k gating network for Mixture of Experts.""" | |
| def __init__( | |
| self, | |
| hidden_size: int, | |
| num_experts: int, | |
| top_k: int = 1, | |
| noisy_gate_policy: Optional[str] = None, | |
| species_emb_dim: Optional[int] = None, | |
| gate_softmax_over_all_experts: bool = False, | |
| ): | |
| super().__init__() | |
| self.hidden_size = hidden_size | |
| self.num_experts = num_experts | |
| self.top_k = top_k | |
| self.noisy_gate_policy = noisy_gate_policy | |
| self.gate_softmax_over_all_experts = gate_softmax_over_all_experts | |
| self.wg = nn.Linear(hidden_size, num_experts, bias=False) | |
| self.use_species_film = species_emb_dim is not None | |
| if self.use_species_film: | |
| self.film_gamma = nn.Linear(species_emb_dim, num_experts, bias=True) | |
| self.film_beta = nn.Linear(species_emb_dim, num_experts, bias=True) | |
| nn.init.trunc_normal_(self.film_gamma.weight, std=0.02) | |
| nn.init.zeros_(self.film_gamma.bias) | |
| nn.init.trunc_normal_(self.film_beta.weight, std=0.02) | |
| nn.init.zeros_(self.film_beta.bias) | |
| else: | |
| self.film_gamma = None | |
| self.film_beta = None | |
| def _compute_aux_loss( | |
| self, | |
| logits: torch.Tensor, | |
| top_k_indices: torch.Tensor, | |
| ) -> torch.Tensor: | |
| """Compute GShard-style load balancing auxiliary loss.""" | |
| num_tokens = logits.shape[0] | |
| flat_indices = top_k_indices.reshape(-1) | |
| expert_counts = torch.zeros(self.num_experts, device=logits.device, dtype=logits.dtype) | |
| expert_counts.scatter_add_(0, flat_indices, torch.ones_like(flat_indices, dtype=logits.dtype)) | |
| f = expert_counts / num_tokens | |
| probs = F.softmax(logits, dim=-1) | |
| p = probs.mean(dim=0) | |
| if self.top_k == 1: | |
| aux_loss = self.num_experts * torch.sum(f * p) | |
| else: | |
| aux_loss = (self.num_experts ** 2) * torch.mean(f * p) / self.top_k | |
| return aux_loss | |
| def forward( | |
| self, | |
| hidden_states: torch.Tensor, | |
| species_emb: Optional[torch.Tensor] = None, | |
| seq_len: Optional[int] = None, | |
| ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]: | |
| """Compute top-k gate scores.""" | |
| if self.training and self.noisy_gate_policy == 'Jitter': | |
| noise = torch.empty_like(hidden_states).uniform_(1.0 - 0.01, 1.0 + 0.01) | |
| hidden_states = hidden_states * noise | |
| logits = F.linear(hidden_states, self.wg.weight, self.wg.bias).float() | |
| if self.use_species_film and species_emb is not None and self.film_gamma is not None: | |
| gamma = F.linear(species_emb, self.film_gamma.weight, self.film_gamma.bias).float() | |
| beta = F.linear(species_emb, self.film_beta.weight, self.film_beta.bias).float() | |
| gamma_expanded = gamma.repeat_interleave(seq_len, dim=0) | |
| beta_expanded = beta.repeat_interleave(seq_len, dim=0) | |
| logits = (1.0 + gamma_expanded) * logits + beta_expanded | |
| if self.gate_softmax_over_all_experts: | |
| all_probs = F.softmax(logits, dim=-1) | |
| gate_scores, top_k_indices = torch.topk(all_probs, self.top_k, dim=-1) | |
| if self.top_k > 1: | |
| gate_scores = gate_scores / gate_scores.sum(dim=-1, keepdim=True) | |
| else: | |
| top_k_logits, top_k_indices = torch.topk(logits, self.top_k, dim=-1) | |
| gate_scores = F.softmax(top_k_logits, dim=-1) | |
| aux_loss = self._compute_aux_loss(logits, top_k_indices) | |
| return gate_scores, top_k_indices, aux_loss | |
| class MoELayer(nn.Module): | |
| """Native PyTorch Mixture of Experts layer.""" | |
| def __init__(self, config: GenomicModelConfig, num_experts: int): | |
| """Initialize native MoE layer.""" | |
| super().__init__() | |
| self.config = config | |
| self.num_experts = num_experts | |
| self.top_k = config.num_experts_per_tok | |
| self.aux_loss_coef = config.router_aux_loss_coef | |
| self.moe_dispatch = getattr(config, "moe_dispatch", "loop") | |
| use_species = config.use_species_encoder or (config.species_emb_dim is not None) | |
| species_dim = config.species_emb_dim if use_species else None | |
| self.gate = TopKGate( | |
| hidden_size=config.hidden_size, | |
| num_experts=num_experts, | |
| top_k=self.top_k, | |
| noisy_gate_policy=config.moe_noisy_gate_policy, | |
| species_emb_dim=species_dim, | |
| gate_softmax_over_all_experts=getattr(config, "gate_softmax_over_all_experts", False), | |
| ) | |
| self.experts = nn.ModuleList([ | |
| SwiGLU(config, dropout_prob=config.moe_expert_dropout_prob) | |
| for _ in range(num_experts) | |
| ]) | |
| self.use_residual = config.moe_use_residual | |
| if self.use_residual: | |
| self.residual_mlp = SwiGLU(config, dropout_prob=config.moe_expert_dropout_prob) | |
| self.residual_mix = nn.Linear(config.hidden_size, 2, bias=True) | |
| self.moe_output_dropout_prob = config.moe_output_dropout_prob | |
| self.moe_output_dropout = ( | |
| nn.Dropout(config.moe_output_dropout_prob) | |
| if config.moe_output_dropout_prob > 0 | |
| else nn.Identity() | |
| ) | |
| def _dispatch_experts(self, flat_hidden, gate_scores, expert_indices): | |
| mode = self.moe_dispatch | |
| if mode == "grouped": | |
| try: | |
| return self._dispatch_grouped(flat_hidden, gate_scores, expert_indices) | |
| except (RuntimeError, NotImplementedError) as e: | |
| if not getattr(self, "_grouped_warned", False): | |
| print(f"[MoELayer] torch._grouped_mm unusable ({type(e).__name__}: " | |
| f"{str(e)[:80]}); falling back to moe_dispatch='sorted'.") | |
| self._grouped_warned = True | |
| self.moe_dispatch = "sorted" | |
| return self._dispatch_sorted(flat_hidden, gate_scores, expert_indices) | |
| if mode == "sorted": | |
| return self._dispatch_sorted(flat_hidden, gate_scores, expert_indices) | |
| return self._dispatch_loop(flat_hidden, gate_scores, expert_indices) | |
| def _dispatch_loop(self, flat_hidden, gate_scores, expert_indices): | |
| """Legacy per-expert Python loop.""" | |
| output = torch.zeros_like(flat_hidden) | |
| for k in range(self.top_k): | |
| indices_k = expert_indices[:, k] | |
| scores_k = gate_scores[:, k] | |
| for expert_idx in range(self.num_experts): | |
| mask = (indices_k == expert_idx) | |
| if not mask.any(): | |
| continue | |
| token_indices = mask.nonzero(as_tuple=True)[0] | |
| expert_input = flat_hidden[token_indices] | |
| expert_output = self.experts[expert_idx](expert_input) | |
| weighted_output = expert_output * scores_k[token_indices].unsqueeze(-1) | |
| output[token_indices] += weighted_output | |
| output = output + sum( | |
| p.flatten()[0] * 0 for expert in self.experts for p in expert.parameters() | |
| ) | |
| return output | |
| def _flatten_assignments(self, gate_scores, expert_indices): | |
| """(token, slot) pairs sorted by expert; shared by sorted/grouped dispatch.""" | |
| num_tokens, top_k = expert_indices.shape | |
| flat_expert = expert_indices.reshape(-1) | |
| flat_score = gate_scores.reshape(-1) | |
| flat_token = torch.arange(num_tokens, device=expert_indices.device).repeat_interleave(top_k) | |
| order = torch.argsort(flat_expert, stable=True) | |
| sorted_token = flat_token[order] | |
| sorted_score = flat_score[order] | |
| counts = torch.bincount(flat_expert, minlength=self.num_experts) | |
| return sorted_token, sorted_score, counts | |
| def _dispatch_sorted(self, flat_hidden, gate_scores, expert_indices): | |
| """Sort tokens by expert, run each expert on its contiguous slice, scatter-add.""" | |
| sorted_token, sorted_score, counts = self._flatten_assignments(gate_scores, expert_indices) | |
| sorted_input = flat_hidden.index_select(0, sorted_token) | |
| sizes = counts.tolist() | |
| chunks = torch.split(sorted_input, sizes, dim=0) | |
| expert_out = torch.cat( | |
| [self.experts[e](chunks[e]) for e in range(self.num_experts)], dim=0 | |
| ) | |
| expert_out = expert_out * sorted_score.to(expert_out.dtype).unsqueeze(-1) | |
| output = torch.zeros_like(flat_hidden) | |
| output.index_add_(0, sorted_token, expert_out.to(output.dtype)) | |
| return output | |
| def _dispatch_grouped(self, flat_hidden, gate_scores, expert_indices): | |
| """Fully batched dispatch via torch._grouped_mm over stacked expert weights.""" | |
| sorted_token, sorted_score, counts = self._flatten_assignments(gate_scores, expert_indices) | |
| sorted_input = flat_hidden.index_select(0, sorted_token).contiguous() | |
| offs = torch.cumsum(counts, 0).to(torch.int32) | |
| w_gate_up = torch.stack([e.gate_up_proj.weight for e in self.experts]) | |
| w_down = torch.stack([e.down_proj.weight for e in self.experts]) | |
| gate_up = torch._grouped_mm( | |
| sorted_input, w_gate_up.transpose(-2, -1), offs=offs | |
| ) | |
| gate, up = gate_up.chunk(2, dim=-1) | |
| act = (F.silu(gate) * up).contiguous() | |
| down = torch._grouped_mm( | |
| act, w_down.transpose(-2, -1), offs=offs | |
| ) | |
| down = down * sorted_score.to(down.dtype).unsqueeze(-1) | |
| output = torch.zeros_like(flat_hidden) | |
| output.index_add_(0, sorted_token, down.to(output.dtype)) | |
| return output | |
| def forward( | |
| self, | |
| hidden_states: torch.Tensor, | |
| species_emb: Optional[torch.Tensor] = None, | |
| ) -> Tuple[torch.Tensor, torch.Tensor]: | |
| """Apply MoE layer with sparse index-based routing.""" | |
| batch_size, seq_len, hidden_size = hidden_states.shape | |
| input_dtype = hidden_states.dtype | |
| num_tokens = batch_size * seq_len | |
| flat_hidden = hidden_states.reshape(num_tokens, hidden_size) | |
| gate_scores, expert_indices, aux_loss = self.gate( | |
| flat_hidden, species_emb=species_emb, seq_len=seq_len | |
| ) | |
| gate_scores = gate_scores.to(input_dtype) | |
| output = self._dispatch_experts(flat_hidden, gate_scores, expert_indices) | |
| if self.use_residual: | |
| residual_output = self.residual_mlp(flat_hidden) | |
| mix_logits = F.linear( | |
| flat_hidden, | |
| self.residual_mix.weight, | |
| self.residual_mix.bias, | |
| ).float() | |
| mix_coef = F.softmax(mix_logits, dim=-1).to(input_dtype) | |
| output = mix_coef[:, 0:1] * output + mix_coef[:, 1:2] * residual_output | |
| output = output.reshape(batch_size, seq_len, hidden_size) | |
| if self.moe_output_dropout_prob > 0 and self.training: | |
| output = self.moe_output_dropout(output) | |
| l_aux = aux_loss.float() * self.aux_loss_coef | |
| return output, l_aux | |
| class GenomicDecoderLayer(nn.Module): | |
| """Single decoder layer for the Genomic Language Model.""" | |
| def __init__(self, config: GenomicModelConfig, layer_idx: int = 0): | |
| super().__init__() | |
| self.config = config | |
| self.layer_idx = layer_idx | |
| self.self_attn = GenomicAttention(config) | |
| self.input_layernorm = nn.RMSNorm(normalized_shape=config.hidden_size) | |
| self.post_attention_layernorm = nn.RMSNorm(normalized_shape=config.hidden_size) | |
| if not config.use_swiglu: | |
| raise NotImplementedError("Only SwiGLU is supported.") | |
| self.num_experts = config.get_num_experts_for_layer(layer_idx) | |
| self.use_moe = self.num_experts > 0 | |
| if self.use_moe: | |
| self.ffn = MoELayer(config, num_experts=self.num_experts) | |
| else: | |
| self.ffn = SwiGLU(config) | |
| def forward( | |
| self, | |
| hidden_states: torch.Tensor, | |
| attention_mask: Optional[torch.Tensor] = None, | |
| species_emb: Optional[torch.Tensor] = None, | |
| ) -> Tuple[torch.Tensor, Optional[torch.Tensor]]: | |
| residual = hidden_states | |
| hidden_states = self.input_layernorm(hidden_states) | |
| hidden_states = self.self_attn(hidden_states, attention_mask=attention_mask) | |
| hidden_states = residual + hidden_states | |
| residual = hidden_states | |
| hidden_states = self.post_attention_layernorm(hidden_states) | |
| moe_loss = None | |
| if self.use_moe: | |
| hidden_states, moe_loss = self.ffn(hidden_states, species_emb=species_emb) | |
| else: | |
| hidden_states = self.ffn(hidden_states) | |
| hidden_states = residual + hidden_states | |
| return hidden_states, moe_loss | |
| class MicroGlotSpeciesTable(nn.Module): | |
| """The pretraining species' 32-d Poincare vectors, addressed by `species_ids` (cf. VITS's speaker table). | |
| `weight` is a float32 buffer `[num_species, 32]` that follows device moves but never dtype casts | |
| (`torch_dtype=...`, `.to(torch.bfloat16)`, `.half()`): the prior is normalised in float32 and then cast. | |
| Row i belongs to line i of the tokenizer's species_vocab.txt. | |
| """ | |
| def __init__(self, num_species: int, dim: int): | |
| super().__init__() | |
| self.register_buffer("weight", torch.zeros(num_species, dim, dtype=torch.float32)) | |
| def _apply(self, fn, recurse=True): | |
| moved = fn(self.weight) | |
| self._buffers["weight"] = moved if moved.dtype == torch.float32 else self.weight.to(moved.device) | |
| return self | |
| def forward(self, species_ids: torch.LongTensor) -> torch.Tensor: | |
| return self.weight[species_ids] | |
| class MicroGlotHead(nn.Module): | |
| """dense -> GELU -> out_proj: the Species-encoder's projection.""" | |
| def __init__(self, in_dim: int, hidden: int, out_dim: int): | |
| super().__init__() | |
| self.dense = nn.Linear(in_dim, hidden) | |
| self.act = nn.GELU() | |
| self.out_proj = nn.Linear(hidden, out_dim) | |
| def forward(self, x): | |
| return self.out_proj(self.act(self.dense(x))) | |
| def _rope_modules(module: nn.Module): | |
| """The flash-attn rotary-embedding modules inside `module`.""" | |
| return [m for m in module.modules() if isinstance(m, RotaryEmbedding)] | |
| class MicroGlotPreTrainedModel(PreTrainedModel): | |
| """Base class of the MicroGlot models: weight initialisation, loading, and the species-encoder calls | |
| (delegated to the `MicroGlotModel` backbone).""" | |
| config_class = MicroGlotConfig | |
| base_model_prefix = "model" | |
| main_input_name = "input_ids" | |
| supports_gradient_checkpointing = True | |
| _no_split_modules = ["GenomicDecoderLayer"] | |
| _keys_to_ignore_on_load_missing = [r"rotary_emb"] | |
| # training-code checkpoints bundle the species encoder (here a separate model); base loads of a causal-LM | |
| # checkpoint carry lm_head. | |
| _keys_to_ignore_on_load_unexpected = [r"(^|\.)species_encoder\.", r"^lm_head\."] | |
| def _init_weights(self, module: nn.Module): | |
| if isinstance(module, nn.Linear): | |
| fan_in = module.weight.shape[1] | |
| std = math.sqrt(self.config.init_scale / fan_in) | |
| nn.init.trunc_normal_(module.weight, std=std) | |
| if module.bias is not None: | |
| nn.init.zeros_(module.bias) | |
| elif isinstance(module, nn.Embedding): | |
| nn.init.trunc_normal_(module.weight, std=0.02) | |
| elif isinstance(module, RotaryEmbedding): | |
| # inv_freq is a non-persistent buffer (not in the checkpoint): transformers>=5 re-creates it | |
| # uninitialised after loading and relies on _init_weights to fill it. | |
| dim, base = module.dim, float(module.base) | |
| module.inv_freq.copy_(1.0 / (base ** (torch.arange(0, dim, 2, dtype=torch.float32) / dim))) | |
| def from_pretrained(cls, pretrained_model_name_or_path, *model_args, species_encoder=None, **kwargs): | |
| """The standard `from_pretrained`, plus `species_encoder`: | |
| species_encoder (`bool` or `str`, *optional*): `True` also loads and attaches the Species-encoder, as | |
| `model.load_species_encoder()` does; a repo id or a local path loads the encoder found there. To | |
| attach an encoder object you already loaded, call `model.set_species_encoder(encoder)`. | |
| """ | |
| if species_encoder is not None and not isinstance(species_encoder, (bool, str, _os.PathLike)): | |
| raise TypeError( | |
| "species_encoder= takes True (this model's Species-encoder), a repo id or a local path. " | |
| "To attach an encoder object you already loaded, call model.set_species_encoder(encoder)." | |
| ) | |
| if _DTYPE_KWARG == "torch_dtype" and "dtype" in kwargs: # accept the newer `dtype=` before transformers 4.56 | |
| kwargs.setdefault("torch_dtype", kwargs.pop("dtype")) | |
| loaded = super().from_pretrained(pretrained_model_name_or_path, *model_args, **kwargs) | |
| model = loaded[0] if isinstance(loaded, tuple) else loaded # output_loading_info=True: (model, info) | |
| # where this model came from: load_species_encoder() looks for the encoder there, at the same revision | |
| if pretrained_model_name_or_path is not None: # None: built from config= and state_dict= | |
| source = _os.fspath(pretrained_model_name_or_path) | |
| if _os.path.isdir(source): | |
| source = _os.path.abspath(source) | |
| revision = getattr(model.config, "_commit_hash", None) or kwargs.get("revision") | |
| for m in (model, getattr(model, "model", None)): | |
| if isinstance(m, MicroGlotModel): | |
| m.__dict__["_microglot_source"] = (source, revision) | |
| if species_encoder: | |
| hub = {k: kwargs[k] for k in ("cache_dir", "force_download", "local_files_only", "token", "proxies") | |
| if k in kwargs} | |
| model.load_species_encoder(None if species_encoder is True else species_encoder, **hub) | |
| return loaded | |
| def to_bfloat16(self) -> "MicroGlotPreTrainedModel": | |
| """Convert model weights to bfloat16 (the species table stays float32).""" | |
| return self.to(torch.bfloat16) | |
| # -- species-encoder composition, delegated to the backbone (MicroGlotModel implements it) ---------- | |
| def _microglot_backbone(self): | |
| return getattr(self, "model", None) | |
| def species_encoder(self): | |
| """The attached species encoder, or None.""" | |
| return self._microglot_backbone().species_encoder | |
| def set_species_encoder(self, encoder, match_backbone: bool = True): | |
| """Attach a species encoder you loaded yourself (None detaches); see `MicroGlotModel.set_species_encoder`.""" | |
| self._microglot_backbone().set_species_encoder(encoder, match_backbone=match_backbone) | |
| def get_species_encoder(self): | |
| """The attached species encoder, or None.""" | |
| return self._microglot_backbone().species_encoder | |
| def load_species_encoder(self, *args, **kwargs): | |
| """Load and attach the species encoder; see `MicroGlotModel.load_species_encoder`.""" | |
| return self._microglot_backbone().load_species_encoder(*args, **kwargs) | |
| def get_species_embeds(self, *args, **kwargs): | |
| """The species vectors the model would use; see `MicroGlotModel.get_species_embeds`.""" | |
| return self._microglot_backbone().get_species_embeds(*args, **kwargs) | |
| def encode_species(self, species, unknown_species: str = "raise") -> torch.LongTensor: | |
| """Species name(s) -> `species_ids` on the model's device: a 0-d tensor for one name, `[n]` for a list. | |
| A thin wrapper over this repository's tokenizer (`tokenizer.convert_species_to_ids`; `None` -> -1, | |
| loose matching, unknown names raise `KeyError` unless `unknown_species="infer"`), loaded once from | |
| where the model was loaded and checked against `config.species_vocab_sha256`. | |
| """ | |
| if not self.config.num_species: | |
| raise ValueError("This checkpoint has no species table (MicroGlot-plain or the species encoder).") | |
| tok = self.__dict__.get("_species_tokenizer") | |
| if tok is None: | |
| from transformers import AutoTokenizer | |
| source = self.config._name_or_path | |
| commit = getattr(self.config, "_commit_hash", None) | |
| try: | |
| tok = AutoTokenizer.from_pretrained(source, trust_remote_code=True, | |
| **({"revision": commit} if commit else {})) | |
| except (OSError, ValueError): | |
| tok = None | |
| if getattr(tok, "species_vocab_sha256", None) is None: | |
| raise ValueError( | |
| f"Could not load the MicroGlot tokenizer from {source!r}. Load it with AutoTokenizer." | |
| "from_pretrained('athanzli/MicroGlot', trust_remote_code=True) and call " | |
| "tokenizer.convert_species_to_ids(names)." | |
| ) | |
| if tok.species_vocab_sha256 != self.config.species_vocab_sha256: | |
| raise ValueError("The tokenizer's species list does not match this model's species table.") | |
| self.__dict__["_species_tokenizer"] = tok | |
| ids = tok.convert_species_to_ids(species, unknown_species=unknown_species) | |
| return torch.tensor(ids, dtype=torch.long, device=self.device) | |
| class MicroGlotModel(MicroGlotPreTrainedModel): | |
| """Decoder-only backbone (`AutoModel`). Returns token states aligned with `input_ids`. | |
| Species inputs: `species_ids` (rows of the built-in table; -1 = infer), `species_embeds` (your own | |
| vectors), or nothing (every row inferred by the attached species encoder). The encoder is held by | |
| reference: not a submodule, so it is never in `parameters()`, `state_dict()` or a saved checkpoint and | |
| never trained, but `.to()/.half()/.cuda()/.float()` reach it. | |
| """ | |
| def __init__(self, config: MicroGlotConfig, species_encoder=None): | |
| super().__init__(config) | |
| self.gradient_checkpointing = False | |
| self.embed_tokens = nn.Embedding(config.vocab_size, config.hidden_size) | |
| self.layers = nn.ModuleList( | |
| [GenomicDecoderLayer(config, layer_idx=i) for i in range(config.num_hidden_layers)] | |
| ) | |
| self.norm = nn.RMSNorm(normalized_shape=config.hidden_size) | |
| self.embed_species = ( | |
| MicroGlotSpeciesTable(config.num_species, config.species_emb_dim) if config.num_species else None | |
| ) | |
| self.species_token_proj = ( | |
| nn.Linear(config.species_emb_dim, config.hidden_size, bias=False) | |
| if config.prepend_species_token | |
| else None | |
| ) | |
| self.__dict__["_species_encoder"] = None | |
| self.__dict__["_species_encoder_follow"] = True | |
| self.post_init() | |
| if species_encoder is not None: | |
| self.set_species_encoder(species_encoder) | |
| def _microglot_backbone(self): | |
| return self | |
| def species_encoder(self): | |
| """The attached species encoder, or None.""" | |
| return self.__dict__.get("_species_encoder") | |
| def set_species_encoder(self, encoder, match_backbone: bool = True): | |
| """Attach (or with None detach) the encoder that infers species for rows without one. | |
| The encoder is put in eval mode with `requires_grad_(False)`. | |
| match_backbone=True (default): move/cast it to the backbone's device and dtype and give it exact rotary | |
| frequencies in the backbone buffer's dtype, so the pair computes exactly what the training code's | |
| combined model computed; later .to()/.half()/.cuda() calls on this model reach it too. match_backbone=False: leave it | |
| where it is (e.g. another GPU); inputs are moved to it and its output is cast back. | |
| """ | |
| if encoder is not None: | |
| if self.config.species_emb_dim is None: | |
| raise ValueError( | |
| "This checkpoint takes no species information (MicroGlot-plain, or the species encoder's own " | |
| "backbone), so it has no use for a species encoder." | |
| ) | |
| if not hasattr(encoder, "projection") or getattr( | |
| encoder.config, "species_encoder_embedding_dim", None | |
| ) != self.config.species_emb_dim: | |
| raise ValueError( | |
| f"Expected the Species-encoder (athanzli/MicroGlot, subfolder='species_encoder') or another " | |
| f"MicroGlotSpeciesEncoder " | |
| f"with {self.config.species_emb_dim}-d output." | |
| ) | |
| encoder.eval().requires_grad_(False) | |
| if match_backbone: | |
| ref = self.embed_tokens.weight | |
| encoder.to(device=ref.device, dtype=ref.dtype) | |
| ref_rope = _rope_modules(self) | |
| if ref_rope: | |
| # The cast above also rounds the encoder's rotary frequencies, which flash-attn reads as | |
| # they are when they are float32. Rebuild them exactly and give them the backbone buffer's | |
| # dtype, as the encoder inside the training code's combined model had. | |
| ref_inv = ref_rope[0].inv_freq | |
| for m in _rope_modules(encoder): | |
| inv_freq = 1.0 / (m.base ** (torch.arange(0, m.dim, 2, dtype=torch.float32) / m.dim)) | |
| m.inv_freq = inv_freq.to(device=ref_inv.device, dtype=ref_inv.dtype) | |
| m._cos_cached, m._seq_len_cached = None, 0 | |
| self.__dict__["_species_encoder"] = encoder | |
| self.__dict__["_species_encoder_follow"] = bool(match_backbone) | |
| def get_species_encoder(self): | |
| """The attached species encoder, or None.""" | |
| return self.species_encoder | |
| def load_species_encoder(self, pretrained_model_name_or_path=None, *, subfolder=None, revision=None, | |
| match_backbone=True, **kwargs): | |
| """Load the Species-encoder and attach it (see `set_species_encoder`); returns the encoder. | |
| Default: the folder `config.species_encoder_subfolder` ("species_encoder") of the repository or local | |
| folder this model was loaded from, at the same revision (or `config.species_encoder_repo` at | |
| `config.species_encoder_revision` if set), in the model's dtype. Other keyword arguments go to | |
| `from_pretrained` (e.g. `cache_dir`, `token`). It uses the encoder class in this code file. | |
| """ | |
| if torch.is_inference_mode_enabled(): | |
| raise RuntimeError( | |
| "Load the species encoder outside torch.inference_mode(): weights created inside it are " | |
| "inference tensors and cannot be moved or cast afterwards." | |
| ) | |
| if self.config.species_emb_dim is None: | |
| raise ValueError("MicroGlot-plain takes no species information, so it has no species encoder.") | |
| path = pretrained_model_name_or_path | |
| if path is None: | |
| if self.config.species_encoder_repo is not None: | |
| path, revision = self.config.species_encoder_repo, revision or self.config.species_encoder_revision | |
| else: | |
| source, source_revision = self.__dict__.get("_microglot_source") or (self.config._name_or_path, None) | |
| path, revision = source, revision or source_revision | |
| subfolder = subfolder if subfolder is not None else self.config.species_encoder_subfolder | |
| if not path: | |
| raise ValueError("Pass the encoder's location, e.g. " | |
| "model.load_species_encoder('athanzli/MicroGlot', subfolder='species_encoder').") | |
| if subfolder: | |
| kwargs["subfolder"] = subfolder | |
| if revision is not None: | |
| kwargs["revision"] = revision | |
| if "torch_dtype" not in kwargs and "dtype" not in kwargs: | |
| kwargs[_DTYPE_KWARG] = self.embed_tokens.weight.dtype | |
| try: | |
| encoder = MicroGlotSpeciesEncoder.from_pretrained(path, **kwargs) | |
| except OSError as e: | |
| raise OSError( | |
| f"No Species-encoder found at {path!r}" + (f" (subfolder {subfolder!r})" if subfolder else "") | |
| + ". Load it from the Hub: model.load_species_encoder('athanzli/MicroGlot', subfolder='species_encoder')." | |
| ) from e | |
| self.set_species_encoder(encoder, match_backbone=match_backbone) | |
| return encoder | |
| def _apply(self, fn, recurse=True): | |
| super()._apply(fn, recurse) | |
| encoder = self.__dict__.get("_species_encoder") | |
| if encoder is not None and self.__dict__.get("_species_encoder_follow", True): | |
| encoder._apply(fn, recurse) | |
| return self | |
| def get_input_embeddings(self) -> nn.Embedding: | |
| return self.embed_tokens | |
| def set_input_embeddings(self, value: nn.Embedding): | |
| self.embed_tokens = value | |
| # -- species resolution ------------------------------------------------------------------------- | |
| def get_species_embeds(self, input_ids=None, attention_mask=None, species_ids=None, species_embeds=None, | |
| normalize_species_embeds: bool = True): | |
| """The [batch, 32] unit vectors this model would condition on, in its dtype, without running the | |
| backbone (prior rows from the table, custom vectors, inferred rows from the attached encoder). | |
| Takes the same species arguments as `forward`; `input_ids` (and `attention_mask`) are only needed for | |
| rows the encoder infers. Use it to inspect inferred species or to precompute a `species_embeds` | |
| column (pass it back with `normalize_species_embeds=False` to reuse the exact vectors). | |
| """ | |
| if input_ids is None: | |
| ref = species_ids if species_ids is not None else species_embeds | |
| if ref is None: | |
| raise ValueError("Pass input_ids, species_ids or species_embeds.") | |
| ref = torch.as_tensor(ref) | |
| n = (ref.shape[0] if ref.dim() > 0 else 1) if species_ids is not None else ( | |
| ref.shape[0] if ref.dim() > 1 else 1) | |
| if species_ids is not None and bool((ref < 0).any()): | |
| raise ValueError("Rows to infer (species_ids == -1) need input_ids.") | |
| input_ids = torch.full((n, 1), self.config.bos_token_id, device=self.embed_tokens.weight.device) | |
| return self._species_vectors(input_ids, attention_mask, species_ids, species_embeds, | |
| normalize_species_embeds) | |
| def _species_vectors(self, input_ids, attention_mask, species_ids, species_embeds, normalize=True): | |
| """Unit-norm species vectors [batch, species_emb_dim] in the model dtype, or None (plain).""" | |
| dim = self.config.species_emb_dim | |
| if dim is None: | |
| if species_ids is not None or species_embeds is not None: | |
| raise ValueError( | |
| "This checkpoint (MicroGlot-plain) takes no species information: drop the species " | |
| "inputs, or load athanzli/MicroGlot to condition on a species." | |
| ) | |
| return None | |
| if species_ids is not None and species_embeds is not None: | |
| raise ValueError("You cannot specify both species_ids and species_embeds at the same time.") | |
| batch, device, dtype = input_ids.shape[0], input_ids.device, self.embed_tokens.weight.dtype | |
| if species_embeds is not None: # the training code's `species_emb=` path (plus a no-op device move) | |
| species_embeds = torch.as_tensor(species_embeds) | |
| if species_embeds.dim() == 1: | |
| species_embeds = species_embeds.unsqueeze(0) | |
| if species_embeds.dim() != 2 or species_embeds.shape[-1] != dim: | |
| raise ValueError( | |
| f"species_embeds must be [{dim}] or [batch, {dim}], got {tuple(species_embeds.shape)}." | |
| ) | |
| if species_embeds.shape[0] == 1 and batch > 1: | |
| species_embeds = species_embeds.expand(batch, -1) | |
| elif species_embeds.shape[0] != batch: | |
| raise ValueError(f"species_embeds has {species_embeds.shape[0]} rows for {batch} sequences.") | |
| species_embeds = species_embeds.to(device=device) | |
| if normalize: | |
| return F.normalize(species_embeds.to(torch.float32), dim=-1).to(dtype) | |
| norms = species_embeds.float().norm(dim=-1) | |
| if bool(((norms - 1).abs() > 1e-2).any()): | |
| raise ValueError( | |
| "normalize_species_embeds=False expects unit vectors that are used as they are (e.g. " | |
| "out.species_embeds or encoder(...).species_embeds)." | |
| ) | |
| return species_embeds.to(dtype) | |
| vectors, unknown = None, None | |
| if species_ids is not None: | |
| species_ids = torch.as_tensor(species_ids, device=device) | |
| if species_ids.dim() == 0: | |
| species_ids = species_ids.expand(batch) | |
| if species_ids.shape != (batch,) or species_ids.dtype not in (torch.int64, torch.int32): | |
| raise ValueError(f"species_ids must be an integer tensor of shape [{batch}].") | |
| species_ids = species_ids.long() | |
| if self.embed_species is None: | |
| raise ValueError("This checkpoint has no species table; pass species_embeds instead.") | |
| n = self.embed_species.weight.shape[0] | |
| if bool(((species_ids < -1) | (species_ids >= n)).any()): | |
| raise ValueError(f"species_ids must lie in [-1, {n}); -1 marks an unknown species.") | |
| prior = self.embed_species(species_ids.clamp(min=0)) | |
| vectors = F.normalize(prior.to(torch.float32), dim=-1).to(dtype) | |
| unknown = species_ids < 0 | |
| if not bool(unknown.any()): | |
| return vectors | |
| encoder = self.species_encoder | |
| if encoder is None: | |
| raise ValueError( | |
| "No species given for some sequences and no species encoder is attached. Pass species= to " | |
| "the tokenizer (or species_ids / species_embeds), or attach the encoder with " | |
| "model.load_species_encoder(). For no species information, use MicroGlot-plain " | |
| "(subfolder='plain')." | |
| ) | |
| rows = slice(None) if unknown is None else unknown | |
| enc_ids = input_ids[rows].to(encoder.device) | |
| enc_mask = None if attention_mask is None else attention_mask[rows].to(encoder.device) | |
| with torch.no_grad(): | |
| inferred = encoder(input_ids=enc_ids, attention_mask=enc_mask).species_embeds | |
| inferred = inferred.to(device=device, dtype=dtype) | |
| if unknown is None: | |
| return inferred | |
| vectors = vectors.clone() | |
| vectors[unknown] = inferred | |
| return vectors | |
| def forward( | |
| self, | |
| input_ids: torch.LongTensor, | |
| attention_mask: Optional[torch.Tensor] = None, | |
| species_embeds: Optional[torch.FloatTensor] = None, | |
| output_hidden_states: Optional[bool] = None, | |
| output_router_logits: Optional[bool] = None, | |
| return_dict: Optional[bool] = None, | |
| species_ids: Optional[torch.LongTensor] = None, | |
| normalize_species_embeds: bool = True, | |
| species_emb: Optional[torch.FloatTensor] = None, | |
| ) -> Union[Tuple, MicroGlotModelOutput]: | |
| """Token states for right-padded `input_ids` `[batch, length]` (each row starts with [BOS]). | |
| Species (MicroGlot only; at most one of the two): | |
| species_ids (`LongTensor` `[batch]`, or 0-d for the whole batch): rows of the species table | |
| (`tokenizer(dna, species=...)` returns them); -1 = infer this row with the attached encoder. | |
| species_embeds (`FloatTensor` `[batch, 32]` or `[32]`): your own vectors, any float dtype and norm; | |
| normalised in float32 and cast to the model dtype. With `normalize_species_embeds=False` they | |
| must already be unit vectors and are used as they are (exact replay of `out.species_embeds`). | |
| `species_emb` (the training code's name) is also accepted. | |
| Neither: every row is inferred by the attached species encoder (ValueError if none is attached). | |
| output_hidden_states: also return the 24 hidden states. output_router_logits: compute `moe_loss` | |
| (default: on). return_dict=False returns the tuple `(last_hidden_state, hidden_states, moe_loss)`. | |
| The positional order is the training code's (the third argument is the species vector). | |
| """ | |
| if not (input_ids.is_cuda and self.embed_tokens.weight.is_cuda): | |
| raise RuntimeError( | |
| "MicroGlot runs on an NVIDIA GPU (flash-attn's rotary kernel is CUDA-only): move the " | |
| "model and the inputs to a CUDA device, e.g. model.to('cuda')." | |
| ) | |
| if species_emb is not None: | |
| if species_embeds is not None: | |
| raise ValueError("Pass species_embeds or species_emb (the same argument), not both.") | |
| species_embeds = species_emb | |
| output_hidden_states = ( | |
| output_hidden_states if output_hidden_states is not None | |
| else self.config.output_hidden_states if hasattr(self.config, 'output_hidden_states') else False | |
| ) | |
| output_router_logits = ( | |
| output_router_logits if output_router_logits is not None | |
| else self.config.use_moe | |
| ) | |
| return_dict = return_dict if return_dict is not None else self.config.return_dict | |
| if attention_mask is not None and not bool(attention_mask[:, 0].all()): | |
| raise ValueError( | |
| "MicroGlot needs right padding (every sequence starts with [BOS] at position 0); " | |
| "tokenize with padding_side='right'." | |
| ) | |
| if attention_mask is None and self.config.pad_token_id is not None and bool( | |
| (input_ids == self.config.pad_token_id).any() | |
| ): | |
| raise ValueError("input_ids contain [PAD] but no attention_mask was given; pass the tokenizer's mask.") | |
| if ( | |
| self.species_token_proj is not None | |
| and (species_ids is not None or species_embeds is not None or self.species_encoder is not None) | |
| and bool((input_ids[:, 0] != self.config.bos_token_id).any()) | |
| ): | |
| raise ValueError( | |
| "With a species, every sequence must start with [BOS] (the species token goes right after it); " | |
| "tokenize with add_special_tokens=True." | |
| ) | |
| seq_len = input_ids.shape[1] | |
| max_trained = getattr(self.config, "max_trained_length", 8192) | |
| if seq_len > max_trained: | |
| warnings.warn( | |
| f"Input sequence length ({seq_len} tokens) exceeds the maximum length seen during " | |
| f"pretraining ({max_trained} tokens); split long sequences into windows as shown under " | |
| "\"Long sequences\" at https://huggingface.co/athanzli/MicroGlot.", | |
| UserWarning, | |
| stacklevel=3, | |
| ) | |
| if torch.is_grad_enabled(): | |
| # flash-attn rebuilds a cos/sin cache made under torch.inference_mode() only in training mode; | |
| # in eval mode it cannot be saved for backward. Drop it (it is rebuilt with the same values). | |
| for layer in self.layers: | |
| rope = layer.self_attn.rotary_emb | |
| if rope._cos_cached is not None and rope._cos_cached.is_inference(): | |
| rope._cos_cached = None | |
| species_emb = self._species_vectors(input_ids, attention_mask, species_ids, species_embeds, | |
| normalize_species_embeds) | |
| # ---- from here on: the forward used in training and benchmarking, unchanged --------------- | |
| hidden_states = self.embed_tokens(input_ids).to(self.embed_tokens.weight.dtype) | |
| if self.species_token_proj is not None and species_emb is not None: | |
| batch_size = hidden_states.size(0) | |
| device = hidden_states.device | |
| species_token = self.species_token_proj(species_emb).unsqueeze(1).to(hidden_states.dtype) | |
| bos_embedding = hidden_states[:, :1, :] | |
| rest_embeddings = hidden_states[:, 1:, :] | |
| hidden_states = torch.cat([bos_embedding, species_token, rest_embeddings], dim=1) | |
| if attention_mask is not None: | |
| species_mask = torch.ones(batch_size, 1, dtype=attention_mask.dtype, device=device) | |
| bos_mask = attention_mask[:, :1] | |
| rest_mask = attention_mask[:, 1:] | |
| attention_mask = torch.cat([bos_mask, species_mask, rest_mask], dim=1) | |
| all_hidden_states = () if output_hidden_states else None | |
| total_moe_loss = torch.tensor(0.0, device=hidden_states.device, dtype=torch.float32) if output_router_logits else None | |
| for layer in self.layers: | |
| if output_hidden_states: | |
| all_hidden_states = all_hidden_states + (hidden_states,) | |
| if self.gradient_checkpointing and self.training: | |
| layer_outputs = checkpoint( | |
| layer, | |
| hidden_states, | |
| attention_mask, | |
| species_emb, | |
| use_reentrant=False, | |
| ) | |
| else: | |
| layer_outputs = layer(hidden_states, attention_mask=attention_mask, species_emb=species_emb) | |
| hidden_states, moe_loss = layer_outputs | |
| if output_router_logits and moe_loss is not None: | |
| total_moe_loss = total_moe_loss + moe_loss | |
| hidden_states = self.norm(hidden_states) | |
| if output_hidden_states: | |
| all_hidden_states = all_hidden_states + (hidden_states,) | |
| species_was_prepended = ( | |
| self.species_token_proj is not None and species_emb is not None | |
| ) | |
| if species_was_prepended: | |
| hidden_states = torch.cat( | |
| [hidden_states[:, :1, :], hidden_states[:, 2:, :]], dim=1 | |
| ) | |
| if all_hidden_states is not None: | |
| all_hidden_states = tuple( | |
| torch.cat([hs[:, :1, :], hs[:, 2:, :]], dim=1) | |
| for hs in all_hidden_states | |
| ) | |
| if not return_dict: | |
| return (hidden_states, all_hidden_states, total_moe_loss) | |
| return MicroGlotModelOutput( | |
| last_hidden_state=hidden_states, | |
| hidden_states=all_hidden_states, | |
| moe_loss=total_moe_loss, | |
| species_embeds=species_emb, | |
| ) | |
| class MicroGlotForCausalLM(MicroGlotPreTrainedModel): | |
| """MicroGlot with its next-token head (`AutoModelForCausalLM`, the released checkpoints' class). | |
| `lm_head` is tied to the input embeddings. Sequence generation is not supported. | |
| """ | |
| _tied_weights_keys = {"lm_head.weight": "model.embed_tokens.weight"} # dict: required by transformers 5 | |
| def __init__(self, config: MicroGlotConfig, species_encoder=None): | |
| super().__init__(config) | |
| self.model = MicroGlotModel(config, species_encoder=species_encoder) | |
| self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False) | |
| self.post_init() | |
| def get_input_embeddings(self) -> nn.Embedding: | |
| return self.model.embed_tokens | |
| def set_input_embeddings(self, value: nn.Embedding): | |
| self.model.embed_tokens = value | |
| def get_output_embeddings(self) -> nn.Linear: | |
| return self.lm_head | |
| def set_output_embeddings(self, new_embeddings: nn.Linear): | |
| self.lm_head = new_embeddings | |
| def tie_weights(self, missing_keys=None, recompute_mapping=True, **kwargs): | |
| """Always tie lm_head to the input embeddings, as in training (also for configs without | |
| tie_word_embeddings); under transformers 5 also clear the tied key from the load report.""" | |
| self.lm_head.weight = self.model.embed_tokens.weight | |
| if missing_keys is not None: | |
| missing_keys.discard("lm_head.weight") | |
| def forward( | |
| self, | |
| input_ids: torch.LongTensor, | |
| attention_mask: Optional[torch.Tensor] = None, | |
| species_embeds: Optional[torch.FloatTensor] = None, | |
| labels: Optional[torch.LongTensor] = None, | |
| output_hidden_states: Optional[bool] = None, | |
| output_router_logits: Optional[bool] = None, | |
| return_dict: Optional[bool] = None, | |
| skip_lm_head: bool = False, | |
| species_ids: Optional[torch.LongTensor] = None, | |
| normalize_species_embeds: bool = True, | |
| species_emb: Optional[torch.FloatTensor] = None, | |
| ) -> Union[Tuple, MicroGlotCausalLMOutput]: | |
| """Next-token logits `[batch, length, vocab]`; the species arguments are those of `MicroGlotModel.forward`. | |
| labels (`LongTensor` `[batch, length]`, -100 = ignored): `loss` = mean next-token cross-entropy. With a | |
| species token the first prediction (made at [BOS], before the model has seen the species) is excluded, | |
| as in pretraining. | |
| output_router_logits: also compute the router load-balancing loss (`moe_loss`) and add it to `loss`, as in | |
| pretraining (default: off). Pass True when training this model, so that the experts stay balanced. | |
| skip_lm_head: return only the final hidden state (in `hidden_states`). | |
| return_dict=False returns the tuple `([loss,] logits, hidden_states, moe_loss)`. | |
| """ | |
| return_dict = return_dict if return_dict is not None else self.config.return_dict | |
| # Off by default, as in transformers' mixture-of-experts language models: `loss` is the next-token | |
| # cross-entropy only. The hidden states and logits do not depend on this flag. | |
| output_router_logits = bool(output_router_logits) | |
| outputs = self.model( | |
| input_ids=input_ids, | |
| attention_mask=attention_mask, | |
| species_ids=species_ids, | |
| species_embeds=species_embeds, | |
| species_emb=species_emb, | |
| normalize_species_embeds=normalize_species_embeds, | |
| output_hidden_states=output_hidden_states, | |
| output_router_logits=output_router_logits, | |
| return_dict=True, | |
| ) | |
| hidden_states = outputs.last_hidden_state | |
| if skip_lm_head: | |
| return MicroGlotCausalLMOutput(loss=None, logits=None, hidden_states=hidden_states) | |
| logits = self.lm_head(hidden_states) | |
| moe_aux_loss = outputs.moe_loss | |
| loss = None | |
| if labels is not None: | |
| species_was_prepended = self.config.prepend_species_token and outputs.species_embeds is not None | |
| if species_was_prepended: | |
| shift_logits = logits[..., 1:-1, :].contiguous() | |
| shift_labels = labels[..., 2:].contiguous() | |
| else: | |
| shift_logits = logits[..., :-1, :].contiguous() | |
| shift_labels = labels[..., 1:].contiguous() | |
| loss_fn = nn.CrossEntropyLoss(ignore_index=-100) | |
| loss = loss_fn( | |
| shift_logits.view(-1, self.config.vocab_size), | |
| shift_labels.view(-1) | |
| ).float() | |
| if self.config.use_moe and moe_aux_loss is not None: | |
| loss = loss + moe_aux_loss | |
| if not return_dict: | |
| output = (logits, outputs.hidden_states, outputs.moe_loss) | |
| return (loss,) + output if loss is not None else output | |
| return MicroGlotCausalLMOutput( | |
| loss=loss, | |
| logits=logits, | |
| hidden_states=outputs.hidden_states, | |
| species_embeds=outputs.species_embeds, | |
| moe_loss=outputs.moe_loss, | |
| ) | |
| class MicroGlotSpeciesEncoder(MicroGlotPreTrainedModel): | |
| """Predicts a species embedding from DNA (folder species_encoder/ of athanzli/MicroGlot, `AutoModel`). | |
| A MicroGlot backbone without species input, pooled at the last real token (needs right padding), then a | |
| Linear -> GELU -> Linear projection to 32 dimensions (`embeddings`), L2-normalised in the model dtype | |
| (`species_embeds`, the vector MicroGlot is conditioned on). | |
| """ | |
| config_class = MicroGlotSpeciesEncoderConfig | |
| def __init__(self, config: MicroGlotSpeciesEncoderConfig): | |
| super().__init__(config) | |
| if config.species_emb_dim is not None or config.prepend_species_token: | |
| raise ValueError("The species encoder's backbone takes no species input (species_emb_dim=None).") | |
| self.embedding_dim = config.species_encoder_embedding_dim | |
| self.pooling_strategy = config.species_encoder_pooling_strategy | |
| self.model = MicroGlotModel(config) | |
| self.projection_dropout = nn.Dropout(config.classifier_dropout_prob) | |
| self.projection = MicroGlotHead(config.hidden_size, config.hidden_size, config.species_encoder_embedding_dim) | |
| self.post_init() | |
| def load_species_encoder(self, *args, **kwargs): | |
| """Not available: this is the species encoder itself.""" | |
| raise TypeError("This is the species encoder; it takes no species encoder of its own.") | |
| def get_input_embeddings(self) -> nn.Embedding: | |
| return self.model.embed_tokens | |
| def set_input_embeddings(self, value: nn.Embedding): | |
| self.model.embed_tokens = value | |
| def forward( | |
| self, | |
| input_ids: torch.LongTensor, | |
| attention_mask: Optional[torch.Tensor] = None, | |
| output_hidden_states: Optional[bool] = None, | |
| return_dict: Optional[bool] = None, | |
| ) -> Union[Tuple, MicroGlotSpeciesEncoderOutput]: | |
| """Species vectors for right-padded `input_ids` `[batch, length]` (one per sequence). | |
| return_dict=False returns `(species_embeds, embeddings, pooler_output, last_hidden_state, | |
| hidden_states)`. | |
| """ | |
| return_dict = return_dict if return_dict is not None else self.config.return_dict | |
| outputs = self.model( | |
| input_ids=input_ids, | |
| attention_mask=attention_mask, | |
| output_hidden_states=output_hidden_states, | |
| output_router_logits=self.config.use_moe, | |
| return_dict=True, | |
| ) | |
| hidden_states = outputs.last_hidden_state | |
| if self.pooling_strategy == "eos": | |
| if attention_mask is not None: | |
| seq_lengths = attention_mask.sum(dim=1) - 1 | |
| seq_lengths = seq_lengths.clamp(min=0) | |
| batch_indices = torch.arange(hidden_states.size(0), device=hidden_states.device) | |
| pooled = hidden_states[batch_indices, seq_lengths] | |
| else: | |
| pooled = hidden_states[:, -1, :] | |
| else: | |
| if attention_mask is not None: | |
| mask = attention_mask.unsqueeze(-1).float() | |
| pooled = (hidden_states * mask).sum(dim=1) / mask.sum(dim=1).clamp(min=1e-9) | |
| else: | |
| pooled = hidden_states.mean(dim=1) | |
| pooled = self.projection_dropout(pooled) | |
| raw = self.projection(pooled) | |
| species_embeds = F.normalize(raw, dim=-1) | |
| if not return_dict: | |
| return (species_embeds, raw, pooled, hidden_states, outputs.hidden_states) | |
| return MicroGlotSpeciesEncoderOutput( | |
| species_embeds=species_embeds, | |
| embeddings=raw, | |
| pooler_output=pooled, | |
| last_hidden_state=hidden_states, | |
| hidden_states=outputs.hidden_states, | |
| moe_loss=outputs.moe_loss, | |
| ) | |
| # Training-code names (configs and auto_maps written by the training code keep resolving). | |
| GenomicPreTrainedModel = MicroGlotPreTrainedModel | |
| GenomicModel = MicroGlotModel | |
| GenomicLMForCausalLM = MicroGlotForCausalLM | |
| GenomicSpeciesEmbeddingEncoder = MicroGlotSpeciesEncoder | |
| GenomicModelOutput = MicroGlotModelOutput | |
| # save_pretrained() then copies this file and writes local auto_map entries, also for Hub-loaded models. | |
| MicroGlotConfig.register_for_auto_class("AutoConfig") | |
| MicroGlotSpeciesEncoderConfig.register_for_auto_class("AutoConfig") | |
| MicroGlotModel.register_for_auto_class("AutoModel") | |
| MicroGlotForCausalLM.register_for_auto_class("AutoModelForCausalLM") | |
| MicroGlotSpeciesEncoder.register_for_auto_class("AutoModel") | |