173 lines
5.3 KiB
Python
173 lines
5.3 KiB
Python
"""Helpers for serving layer-scoped model files from tracker-local snapshots."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import re
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
INDEX_FILENAME = "model.safetensors.index.json"
|
|
|
|
_LAYER_RE = re.compile(
|
|
r"(?:^|\.)"
|
|
r"(?:model\.layers|layers|h|blocks|decoder\.layers|encoder\.layers)"
|
|
r"\.(\d+)(?:\.|$)"
|
|
)
|
|
|
|
_METADATA_FILENAMES = {
|
|
INDEX_FILENAME,
|
|
"config.json",
|
|
"generation_config.json",
|
|
"preprocessor_config.json",
|
|
"special_tokens_map.json",
|
|
"tokenizer.json",
|
|
"tokenizer.model",
|
|
"tokenizer_config.json",
|
|
"vocab.json",
|
|
"merges.txt",
|
|
"added_tokens.json",
|
|
}
|
|
|
|
_METADATA_PREFIXES = ("config.", "tokenizer.", "tokenizer_", "vocab.")
|
|
|
|
_HEAD_MARKERS = ("embed", "embedding", "embed_tokens", "wte", "wpe")
|
|
|
|
_TAIL_EXACT = {
|
|
"lm_head.weight",
|
|
"lm_head.bias",
|
|
"model.norm.weight",
|
|
"model.norm.bias",
|
|
"transformer.ln_f.weight",
|
|
"transformer.ln_f.bias",
|
|
"decoder.final_layer_norm.weight",
|
|
"decoder.final_layer_norm.bias",
|
|
}
|
|
|
|
_TAIL_MARKERS = (".lm_head.", ".norm.", ".ln_f.", ".final_layer_norm.")
|
|
|
|
|
|
def snapshot_dir_for_repo(models_dir: Path, repo_id: str) -> Path | None:
|
|
"""Return the most likely local HF snapshot directory for *repo_id*."""
|
|
candidates = [
|
|
models_dir / repo_id,
|
|
models_dir / repo_id.replace("/", "--"),
|
|
models_dir / f"models--{repo_id.replace('/', '--')}",
|
|
]
|
|
for candidate in candidates:
|
|
if (candidate / "snapshots").is_dir():
|
|
snapshots = sorted(p for p in (candidate / "snapshots").iterdir() if p.is_dir())
|
|
if snapshots:
|
|
return snapshots[-1]
|
|
if candidate.is_dir():
|
|
return candidate
|
|
return None
|
|
|
|
|
|
def files_for_layer_range(snapshot_dir: Path, shard_start: int, shard_end: int) -> list[str]:
|
|
"""Select files needed to load a conservative safetensors shard subset."""
|
|
return select_safetensors_files_for_layers(snapshot_dir, shard_start, shard_end)
|
|
|
|
|
|
def select_safetensors_files_for_layers(
|
|
model_dir: str | Path,
|
|
start_layer: int,
|
|
end_layer: int,
|
|
*,
|
|
total_layers: int | None = None,
|
|
) -> list[str]:
|
|
if start_layer < 0:
|
|
raise ValueError("start_layer must be non-negative")
|
|
if end_layer < start_layer:
|
|
raise ValueError("end_layer must be greater than or equal to start_layer")
|
|
|
|
root = Path(model_dir)
|
|
index_path = root / INDEX_FILENAME
|
|
try:
|
|
index = json.loads(index_path.read_text(encoding="utf-8"))
|
|
except FileNotFoundError:
|
|
return sorted(p.name for p in root.glob("*.safetensors") if p.is_file())
|
|
|
|
weight_map = index.get("weight_map")
|
|
if not isinstance(weight_map, dict):
|
|
raise ValueError(f"{INDEX_FILENAME} must contain a weight_map object")
|
|
|
|
inferred_total_layers = total_layers if total_layers is not None else _read_total_layers(root)
|
|
selected = _metadata_files(root)
|
|
|
|
for tensor_name, rel_file in weight_map.items():
|
|
if not isinstance(tensor_name, str) or not isinstance(rel_file, str):
|
|
continue
|
|
if _tensor_belongs_to_range(tensor_name, start_layer, end_layer, inferred_total_layers):
|
|
selected.add(_normalise_relative_file(rel_file))
|
|
|
|
return sorted(rel for rel in selected if (root / rel).is_file())
|
|
|
|
|
|
def _tensor_belongs_to_range(
|
|
tensor_name: str,
|
|
start_layer: int,
|
|
end_layer: int,
|
|
total_layers: int | None,
|
|
) -> bool:
|
|
layer = _layer_index(tensor_name)
|
|
if layer is not None:
|
|
return start_layer <= layer <= end_layer
|
|
if start_layer == 0 and _is_head_tensor(tensor_name):
|
|
return True
|
|
if total_layers is not None and end_layer >= total_layers - 1 and _is_tail_tensor(tensor_name):
|
|
return True
|
|
return False
|
|
|
|
|
|
def _layer_index(tensor_name: str) -> int | None:
|
|
match = _LAYER_RE.search(tensor_name)
|
|
return int(match.group(1)) if match else None
|
|
|
|
|
|
def _is_head_tensor(tensor_name: str) -> bool:
|
|
lowered = tensor_name.lower()
|
|
return any(marker in lowered for marker in _HEAD_MARKERS)
|
|
|
|
|
|
def _is_tail_tensor(tensor_name: str) -> bool:
|
|
lowered = tensor_name.lower()
|
|
return lowered in _TAIL_EXACT or any(marker in lowered for marker in _TAIL_MARKERS)
|
|
|
|
|
|
def _metadata_files(root: Path) -> set[str]:
|
|
files = {INDEX_FILENAME}
|
|
for path in root.iterdir():
|
|
if not path.is_file():
|
|
continue
|
|
name = path.name
|
|
if name in _METADATA_FILENAMES or name.startswith(_METADATA_PREFIXES):
|
|
files.add(name)
|
|
return files
|
|
|
|
|
|
def _read_total_layers(root: Path) -> int | None:
|
|
config_path = root / "config.json"
|
|
if not config_path.exists():
|
|
return None
|
|
config = json.loads(config_path.read_text(encoding="utf-8"))
|
|
return _layers_from_config(config)
|
|
|
|
|
|
def _layers_from_config(config: dict[str, Any]) -> int | None:
|
|
for key in ("num_hidden_layers", "num_layers", "n_layer", "n_layers"):
|
|
value = config.get(key)
|
|
if isinstance(value, int) and value > 0:
|
|
return value
|
|
text_config = config.get("text_config")
|
|
if isinstance(text_config, dict):
|
|
return _layers_from_config(text_config)
|
|
return None
|
|
|
|
|
|
def _normalise_relative_file(rel_file: str) -> str:
|
|
path = Path(rel_file)
|
|
if path.is_absolute() or ".." in path.parts:
|
|
raise ValueError(f"unsafe relative file in {INDEX_FILENAME}: {rel_file}")
|
|
return path.as_posix()
|