Spaces:
Sleeping
Sleeping
| from dataclasses import dataclass | |
| from typing import Callable, Dict, Tuple, List, TYPE_CHECKING | |
| import os | |
| import json | |
| if TYPE_CHECKING: | |
| import torch | |
| class ModelConfig: | |
| """Configuration for model-specific weight extraction.""" | |
| repo_id: str | |
| config_fields: Dict[str, str] | |
| extract_qkv: Callable[ | |
| [str, int, int, Dict], Tuple["torch.Tensor", "torch.Tensor", "torch.Tensor"] | |
| ] | |
| revisions: List[str] | |
| allow_patterns: List[str] | |
| qkv_scale_factor: float = 1.0 # Scaling factor applied to W_Q, W_K, W_V after extraction | |
| # Optional extractors — set per architecture. None means "not available". | |
| extract_o: "Callable | None" = None # (cache_path, layer_idx, d_model, weight_map, device) -> W_O | |
| extract_biases: "Callable | None" = None # (cache_path, layer_idx, d_model, weight_map, device) -> dict | |
| def get_config_value(self, config_dict: Dict, standard_name: str) -> int: | |
| """Extract a config value using the model-specific field name.""" | |
| model_field = self.config_fields[standard_name] | |
| return config_dict[model_field] | |
| # ============================================================================ | |
| # Extraction Functions | |
| # ============================================================================ | |
| def extract_shard_path( | |
| cache_path: str, key: str, weight_map: Dict = None, binfile_name="pytorch_model.bin" | |
| ): | |
| if weight_map: | |
| shard = weight_map[key] | |
| return os.path.join(cache_path, shard) | |
| else: | |
| return os.path.join(cache_path, binfile_name) | |
| def extract_weight_map(cache_path: str, index_file_name="pytorch_model.bin.index.json"): | |
| index_file = os.path.join(cache_path, index_file_name) | |
| if os.path.exists(index_file): | |
| with open(index_file) as f: | |
| weight_map = json.load(f)["weight_map"] | |
| else: | |
| weight_map = None # Single file model | |
| return weight_map | |
| def get_safetensors_path(cache_path, key): | |
| """Find which shard contains the key.""" | |
| index_path = os.path.join(cache_path, "model.safetensors.index.json") | |
| if os.path.exists(index_path): | |
| with open(index_path) as f: | |
| weight_map = json.load(f)["weight_map"] | |
| shard = weight_map[key] | |
| return os.path.join(cache_path, shard) | |
| else: | |
| # Single file | |
| return os.path.join(cache_path, "model.safetensors") | |
| def extract_pythia_qkv( | |
| cache_path: str, layer_idx: int, d_model: int, weight_map: Dict = None, device: str = "cpu", qkv_scale_factor: float = 1.0 | |
| ) -> Tuple["torch.Tensor", "torch.Tensor", "torch.Tensor"]: | |
| """Extract Q, K, V weights for Pythia/GPT-NeoX models with memory-mapped loading.""" | |
| import torch | |
| key = f"gpt_neox.layers.{layer_idx}.attention.query_key_value.weight" | |
| shard_path = extract_shard_path( | |
| cache_path=cache_path, | |
| key=key, | |
| weight_map=weight_map, | |
| binfile_name="pytorch_model.bin", | |
| ) | |
| state_dict = torch.load(shard_path, map_location=device, mmap=True) | |
| qkv = state_dict[key].clone() | |
| del state_dict | |
| W_Q, W_K, W_V = qkv.chunk(3, dim=0) | |
| return W_Q * qkv_scale_factor, W_K * qkv_scale_factor, W_V * qkv_scale_factor | |
| def extract_gpt2_qkv( | |
| cache_path: str, layer_idx: int, d_model: int, weight_map: Dict = None, device: str = "cpu", qkv_scale_factor: float = 1.0 | |
| ) -> Tuple["torch.Tensor", "torch.Tensor", "torch.Tensor"]: | |
| """Extract Q, K, V weights for GPT-2 models using safetensors.""" | |
| from safetensors import safe_open | |
| import os | |
| # GPT-2 uses safetensors (single file, not sharded) | |
| safetensors_path = os.path.join(cache_path, "model.safetensors") | |
| key = f"h.{layer_idx}.attn.c_attn.weight" | |
| with safe_open(safetensors_path, framework="pt", device=device) as f: | |
| c_attn = f.get_tensor(key).T.clone() | |
| W_Q, W_K, W_V = c_attn.chunk(3, dim=0) | |
| return W_Q * qkv_scale_factor, W_K * qkv_scale_factor, W_V * qkv_scale_factor | |
| def _read_num_attention_heads(cache_path: str) -> int: | |
| with open(os.path.join(cache_path, "config.json")) as f: | |
| return json.load(f)["num_attention_heads"] | |
| def extract_llama_qkv( | |
| cache_path: str, layer_idx: int, d_model: int, weight_map: Dict = None, device: str = "cpu", qkv_scale_factor: float = 1.0 | |
| ) -> Tuple["torch.Tensor", "torch.Tensor", "torch.Tensor"]: | |
| """Extract Q, K, V for LLaMA models.""" | |
| from safetensors import safe_open | |
| q_key = f"model.layers.{layer_idx}.self_attn.q_proj.weight" | |
| k_key = f"model.layers.{layer_idx}.self_attn.k_proj.weight" | |
| v_key = f"model.layers.{layer_idx}.self_attn.v_proj.weight" | |
| q_path = get_safetensors_path(cache_path, q_key) | |
| k_path = get_safetensors_path(cache_path, k_key) | |
| v_path = get_safetensors_path(cache_path, v_key) | |
| with safe_open(q_path, framework="pt", device=device) as f: | |
| W_Q = f.get_tensor(q_key).clone() | |
| with safe_open(k_path, framework="pt", device=device) as f: | |
| W_K = f.get_tensor(k_key).clone() | |
| with safe_open(v_path, framework="pt", device=device) as f: | |
| W_V = f.get_tensor(v_key).clone() | |
| # GQA: repeat at the head-block level, not row-by-row, so downstream | |
| # reshape(n_heads, head_dim, d_model) gives each query head a faithful copy | |
| # of its kv head rather than head_dim/repeat_factor rows of it. | |
| n_heads = _read_num_attention_heads(cache_path) | |
| head_dim = W_Q.shape[0] // n_heads | |
| n_kv = W_K.shape[0] // head_dim | |
| repeat_factor = n_heads // n_kv | |
| W_K = W_K.reshape(n_kv, head_dim, d_model).repeat_interleave(repeat_factor, dim=0).reshape(n_heads * head_dim, d_model) | |
| W_V = W_V.reshape(n_kv, head_dim, d_model).repeat_interleave(repeat_factor, dim=0).reshape(n_heads * head_dim, d_model) | |
| return W_Q * qkv_scale_factor, W_K * qkv_scale_factor, W_V * qkv_scale_factor | |
| def extract_mistral_qkv( | |
| cache_path: str, layer_idx: int, d_model: int, weight_map: Dict = None, device: str = "cpu", qkv_scale_factor: float = 1.0 | |
| ) -> Tuple["torch.Tensor", "torch.Tensor", "torch.Tensor"]: | |
| """Extract Q, K, V for mistral models. | |
| Supports both consolidated and sharded safetensors formats. | |
| Prefers consolidated if available, otherwise uses sharded format. | |
| """ | |
| from safetensors import safe_open | |
| import os | |
| # Try consolidated format first (if it exists) | |
| consolidated_path = os.path.join(cache_path, "consolidated.safetensors") | |
| if os.path.exists(consolidated_path): | |
| safetensors_path = consolidated_path | |
| q_key = f"layers.{layer_idx}.attention.wq.weight" | |
| k_key = f"layers.{layer_idx}.attention.wk.weight" | |
| v_key = f"layers.{layer_idx}.attention.wv.weight" | |
| with safe_open(safetensors_path, framework="pt", device=device) as f: | |
| W_Q = f.get_tensor(q_key).clone() | |
| W_K = f.get_tensor(k_key).clone() | |
| W_V = f.get_tensor(v_key).clone() | |
| else: | |
| # Use sharded format with index | |
| q_key = f"model.layers.{layer_idx}.self_attn.q_proj.weight" | |
| k_key = f"model.layers.{layer_idx}.self_attn.k_proj.weight" | |
| v_key = f"model.layers.{layer_idx}.self_attn.v_proj.weight" | |
| q_path = get_safetensors_path(cache_path, q_key) | |
| k_path = get_safetensors_path(cache_path, k_key) | |
| v_path = get_safetensors_path(cache_path, v_key) | |
| with safe_open(q_path, framework="pt", device=device) as f: | |
| W_Q = f.get_tensor(q_key).clone() | |
| with safe_open(k_path, framework="pt", device=device) as f: | |
| W_K = f.get_tensor(k_key).clone() | |
| with safe_open(v_path, framework="pt", device=device) as f: | |
| W_V = f.get_tensor(v_key).clone() | |
| # GQA: repeat at the head-block level, not row-by-row. See extract_llama_qkv. | |
| n_heads = _read_num_attention_heads(cache_path) | |
| head_dim = W_Q.shape[0] // n_heads | |
| n_kv = W_K.shape[0] // head_dim | |
| repeat_factor = n_heads // n_kv | |
| W_K = W_K.reshape(n_kv, head_dim, d_model).repeat_interleave(repeat_factor, dim=0).reshape(n_heads * head_dim, d_model) | |
| W_V = W_V.reshape(n_kv, head_dim, d_model).repeat_interleave(repeat_factor, dim=0).reshape(n_heads * head_dim, d_model) | |
| return W_Q * qkv_scale_factor, W_K * qkv_scale_factor, W_V * qkv_scale_factor | |
| # ============================================================================ | |
| # W_O Extraction Functions | |
| # ============================================================================ | |
| def extract_pythia_o( | |
| cache_path: str, layer_idx: int, d_model: int, weight_map: Dict = None, | |
| device: str = "cpu", | |
| ) -> "torch.Tensor": | |
| """Extract output projection W_O for Pythia/GPT-NeoX models. | |
| Returns W_O of shape (d_model, d_model). | |
| """ | |
| import torch | |
| key = f"gpt_neox.layers.{layer_idx}.attention.dense.weight" | |
| shard_path = extract_shard_path(cache_path, key, weight_map, "pytorch_model.bin") | |
| state_dict = torch.load(shard_path, map_location=device, mmap=True) | |
| W_O = state_dict[key].clone() | |
| del state_dict | |
| return W_O | |
| def extract_gpt2_o( | |
| cache_path: str, layer_idx: int, d_model: int, weight_map: Dict = None, | |
| device: str = "cpu", | |
| ) -> "torch.Tensor": | |
| """Extract output projection W_O for GPT-2 models. | |
| GPT-2 c_proj.weight is stored transposed: shape (d_model, d_model). | |
| Convention: W_O maps from d_model -> d_model as output = W_O @ head_output. | |
| """ | |
| from safetensors import safe_open | |
| import os | |
| safetensors_path = os.path.join(cache_path, "model.safetensors") | |
| key = f"h.{layer_idx}.attn.c_proj.weight" | |
| with safe_open(safetensors_path, framework="pt", device=device) as f: | |
| W_O = f.get_tensor(key).T.clone() # transpose to (d_model, d_model) | |
| return W_O | |
| def extract_llama_o( | |
| cache_path: str, layer_idx: int, d_model: int, weight_map: Dict = None, | |
| device: str = "cpu", | |
| ) -> "torch.Tensor": | |
| """Extract output projection W_O for LLaMA models.""" | |
| from safetensors import safe_open | |
| o_key = f"model.layers.{layer_idx}.self_attn.o_proj.weight" | |
| o_path = get_safetensors_path(cache_path, o_key) | |
| with safe_open(o_path, framework="pt", device=device) as f: | |
| W_O = f.get_tensor(o_key).clone() | |
| return W_O | |
| def extract_mistral_o( | |
| cache_path: str, layer_idx: int, d_model: int, weight_map: Dict = None, | |
| device: str = "cpu", | |
| ) -> "torch.Tensor": | |
| """Extract output projection W_O for Mistral models.""" | |
| from safetensors import safe_open | |
| import os | |
| consolidated_path = os.path.join(cache_path, "consolidated.safetensors") | |
| if os.path.exists(consolidated_path): | |
| o_key = f"layers.{layer_idx}.attention.wo.weight" | |
| safetensors_path = consolidated_path | |
| else: | |
| o_key = f"model.layers.{layer_idx}.self_attn.o_proj.weight" | |
| safetensors_path = get_safetensors_path(cache_path, o_key) | |
| with safe_open(safetensors_path, framework="pt", device=device) as f: | |
| W_O = f.get_tensor(o_key).clone() | |
| return W_O | |
| # ============================================================================ | |
| # Bias Extraction Functions | |
| # ============================================================================ | |
| def extract_pythia_biases( | |
| cache_path: str, layer_idx: int, d_model: int, weight_map: Dict = None, | |
| device: str = "cpu", | |
| ) -> Dict[str, "torch.Tensor"]: | |
| """Extract attention biases for Pythia/GPT-NeoX. | |
| Returns dict with keys: 'b_Q', 'b_K', 'b_V', 'b_O' (each 1-d tensors). | |
| Missing biases are omitted from the dict. | |
| """ | |
| import torch | |
| biases = {} | |
| # QKV bias | |
| qkv_key = f"gpt_neox.layers.{layer_idx}.attention.query_key_value.bias" | |
| shard_path = extract_shard_path(cache_path, qkv_key, weight_map, "pytorch_model.bin") | |
| try: | |
| state_dict = torch.load(shard_path, map_location=device, mmap=True) | |
| if qkv_key in state_dict: | |
| qkv_bias = state_dict[qkv_key].clone() | |
| b_Q, b_K, b_V = qkv_bias.chunk(3, dim=0) | |
| biases["b_Q"] = b_Q | |
| biases["b_K"] = b_K | |
| biases["b_V"] = b_V | |
| del state_dict | |
| except (KeyError, FileNotFoundError): | |
| pass | |
| # Output bias | |
| o_key = f"gpt_neox.layers.{layer_idx}.attention.dense.bias" | |
| shard_path = extract_shard_path(cache_path, o_key, weight_map, "pytorch_model.bin") | |
| try: | |
| state_dict = torch.load(shard_path, map_location=device, mmap=True) | |
| if o_key in state_dict: | |
| biases["b_O"] = state_dict[o_key].clone() | |
| del state_dict | |
| except (KeyError, FileNotFoundError): | |
| pass | |
| return biases | |
| def extract_gpt2_biases( | |
| cache_path: str, layer_idx: int, d_model: int, weight_map: Dict = None, | |
| device: str = "cpu", | |
| ) -> Dict[str, "torch.Tensor"]: | |
| """Extract attention biases for GPT-2. | |
| GPT-2 has biases on both c_attn (QKV) and c_proj (O). | |
| """ | |
| from safetensors import safe_open | |
| import os | |
| biases = {} | |
| safetensors_path = os.path.join(cache_path, "model.safetensors") | |
| # QKV bias | |
| qkv_key = f"h.{layer_idx}.attn.c_attn.bias" | |
| try: | |
| with safe_open(safetensors_path, framework="pt", device=device) as f: | |
| qkv_bias = f.get_tensor(qkv_key).clone() | |
| b_Q, b_K, b_V = qkv_bias.chunk(3, dim=0) | |
| biases["b_Q"] = b_Q | |
| biases["b_K"] = b_K | |
| biases["b_V"] = b_V | |
| except (KeyError, Exception): | |
| pass | |
| # Output bias | |
| o_key = f"h.{layer_idx}.attn.c_proj.bias" | |
| try: | |
| with safe_open(safetensors_path, framework="pt", device=device) as f: | |
| biases["b_O"] = f.get_tensor(o_key).clone() | |
| except (KeyError, Exception): | |
| pass | |
| return biases | |
| def extract_llama_biases( | |
| cache_path: str, layer_idx: int, d_model: int, weight_map: Dict = None, | |
| device: str = "cpu", | |
| ) -> Dict[str, "torch.Tensor"]: | |
| """Extract attention biases for LLaMA. | |
| LLaMA typically has no attention biases (bias=False). Returns empty dict. | |
| """ | |
| return {} | |
| def extract_mistral_biases( | |
| cache_path: str, layer_idx: int, d_model: int, weight_map: Dict = None, | |
| device: str = "cpu", | |
| ) -> Dict[str, "torch.Tensor"]: | |
| """Extract attention biases for Mistral. | |
| Mistral typically has no attention biases. Returns empty dict. | |
| """ | |
| return {} | |
| # ============================================================================ | |
| # Model Registry | |
| # ============================================================================ | |
| # | |
| # Pythia models to register | |
| PYTHIA_MODELS = [ | |
| "pythia-70m-deduped", | |
| "pythia-160m-deduped", | |
| "pythia-410m-deduped", | |
| "pythia-1b-deduped", | |
| "pythia-1.4b-deduped", | |
| "pythia-2.8b-deduped", | |
| "pythia-6.9b-deduped", | |
| "pythia-12b-deduped", | |
| ] | |
| PYTHIA_REVISIONS = [ | |
| "step0", | |
| "step1", | |
| "step2", | |
| "step4", | |
| "step8", | |
| "step16", | |
| "step32", | |
| "step64", | |
| "step128", | |
| "step256", | |
| "step512", | |
| ] + [f"step{step}" for step in range(1000, 144000, 1000)] | |
| # Common config for all Pythia models | |
| PYTHIA_CONFIG_FIELDS = { | |
| "n_layers": "num_hidden_layers", | |
| "d_model": "hidden_size", | |
| "n_heads": "num_attention_heads", | |
| } | |
| # Build MODEL_CONFIGS registry | |
| MODEL_CONFIGS = {} | |
| # Add Pythia models | |
| for pythia_model in PYTHIA_MODELS: | |
| MODEL_CONFIGS[pythia_model] = ModelConfig( | |
| repo_id=f"EleutherAI/{pythia_model}", | |
| config_fields=PYTHIA_CONFIG_FIELDS, | |
| extract_qkv=extract_pythia_qkv, | |
| revisions=PYTHIA_REVISIONS, | |
| allow_patterns=["*.bin", "*.json"], | |
| extract_o=extract_pythia_o, | |
| extract_biases=extract_pythia_biases, | |
| ) | |
| # Add GPT-2 models | |
| GPT2_MODELS = ["gpt2", "gpt2-medium", "gpt2-large", "gpt2-xl"] | |
| GPT2_CONFIG_FIELDS = { | |
| "n_layers": "n_layer", | |
| "d_model": "n_embd", | |
| "n_heads": "n_head", | |
| } | |
| for gpt2_model in GPT2_MODELS: | |
| MODEL_CONFIGS[gpt2_model] = ModelConfig( | |
| repo_id=f"openai-community/{gpt2_model}", | |
| config_fields=GPT2_CONFIG_FIELDS, | |
| extract_qkv=extract_gpt2_qkv, | |
| revisions=[], | |
| allow_patterns=["*.safetensors", "config.json"], | |
| extract_o=extract_gpt2_o, | |
| extract_biases=extract_gpt2_biases, | |
| ) | |
| # Add LLaMA models | |
| LLAMA_MODELS = ["llama-3.1-8b", "llama-3.1-70b", "llama-3.2-1b", "llama-3.2-3b"] | |
| LLAMA_CONFIG_FIELDS = { | |
| "n_layers": "num_hidden_layers", | |
| "d_model": "hidden_size", | |
| "n_heads": "num_attention_heads", | |
| } | |
| for llama_model in LLAMA_MODELS: | |
| MODEL_CONFIGS[llama_model] = ModelConfig( | |
| repo_id=f"meta-llama/{llama_model}", | |
| config_fields=LLAMA_CONFIG_FIELDS, | |
| extract_qkv=extract_llama_qkv, | |
| revisions=[], | |
| allow_patterns=["*.safetensors", "model.safetensors.index.json", "config.json"], | |
| extract_o=extract_llama_o, | |
| extract_biases=extract_llama_biases, | |
| ) | |
| # Add Mistral models | |
| MISTRAL_MODELS = ["mistral-7b-v0.3", "mixtral-8x7b-v0.1", "mixtral-8x22b-v0.1"] | |
| MISTRAL_CONFIG_FIELDS = { | |
| "n_layers": "num_hidden_layers", | |
| "d_model": "hidden_size", | |
| "n_heads": "num_attention_heads", | |
| } | |
| for mistral_model in MISTRAL_MODELS: | |
| MODEL_CONFIGS[mistral_model] = ModelConfig( | |
| repo_id=f"mistralai/{mistral_model}", | |
| config_fields=MISTRAL_CONFIG_FIELDS, | |
| extract_qkv=extract_mistral_qkv, | |
| revisions=[], | |
| # Exclude consolidated.safetensors to avoid downloading duplicate weights | |
| # The model-*-of-*.safetensors files contain the same weights in sharded format | |
| allow_patterns=["model-*.safetensors", "model.safetensors.index.json", "config.json"], | |
| qkv_scale_factor=5.66, # Suspected factor of sqrt(n_heads) for Mistral models | |
| extract_o=extract_mistral_o, | |
| extract_biases=extract_mistral_biases, | |
| ) | |
| def get_model_config(model_name: str) -> ModelConfig: | |
| """ | |
| Get the configuration for a given model. | |
| Args: | |
| model_name: Name of the model (e.g., 'pythia-70m-deduped', 'gpt2') | |
| Returns: | |
| ModelConfig object containing repo_id, config_fields, and extract_qkv | |
| Raises: | |
| ValueError: If model_name is not in registry | |
| """ | |
| if model_name not in MODEL_CONFIGS: | |
| available = ", ".join(MODEL_CONFIGS.keys()) | |
| raise ValueError(f"Unknown model: {model_name}\nAvailable models: {available}") | |
| return MODEL_CONFIGS[model_name] | |
| def list_supported_models() -> list[str]: | |
| """Return list of all supported model names.""" | |
| return sorted(MODEL_CONFIGS.keys()) | |
| def is_model_supported(model_name: str) -> bool: | |
| """Check if a model is supported.""" | |
| return model_name in MODEL_CONFIGS | |
| def get_model_versions(model_name: str): | |
| mc = get_model_config(model_name=model_name) | |
| return mc.revisions | |