Files
DeepHealth/model_architectures.py

132 lines
4.0 KiB
Python
Raw Normal View History

"""Model-architecture identifiers and checkpoint validation helpers."""
from __future__ import annotations
import re
from collections.abc import Mapping
TRANSFORMER_FFN_ARCHITECTURE = "transformer_ffn_v1"
TRAJ_MIXER_ARCHITECTURE = "traj_mixer_v5"
DEFAULT_MODEL_ARCHITECTURE = TRANSFORMER_FFN_ARCHITECTURE
SUPPORTED_MODEL_ARCHITECTURES = (
TRANSFORMER_FFN_ARCHITECTURE,
TRAJ_MIXER_ARCHITECTURE,
)
_FFN_STATE_KEY = re.compile(
r"(?:^|\.)blocks\.\d+\.mlp\.w[123]\.(?:weight|bias)$"
)
_TRAJ_MIXER_STATE_KEY = re.compile(
r"(?:^|\.)blocks\.\d+\.mlp\.(?:"
r"norm\.(?:weight|bias)|"
r"intra_gate_proj|"
r"intra_value_proj|"
r"intra_output_proj|"
r"intra_gate_logits|"
r"gate_proj|"
r"value_proj|"
r"output_proj"
r")$"
)
def _validate_model_architecture(model_architecture: object) -> str:
if not isinstance(model_architecture, str):
raise ValueError(
"model_architecture must be one of "
f"{SUPPORTED_MODEL_ARCHITECTURES}, got {model_architecture!r}"
)
if model_architecture not in SUPPORTED_MODEL_ARCHITECTURES:
raise ValueError(
f"Unsupported model_architecture={model_architecture!r}; "
f"expected one of {SUPPORTED_MODEL_ARCHITECTURES}."
)
return model_architecture
def detect_model_architecture_from_state_dict(
state_dict: Mapping[str, object],
) -> str:
"""Infer the architecture from block parameter names.
Detection deliberately accepts any ``blocks.<index>`` prefix rather than
assuming that block zero is present.
"""
if not isinstance(state_dict, Mapping):
raise TypeError(
"state_dict must be a mapping, got "
f"{type(state_dict).__name__}"
)
has_ffn = False
has_traj_mixer = False
for raw_key in state_dict:
key = str(raw_key)
has_ffn = has_ffn or _FFN_STATE_KEY.search(key) is not None
has_traj_mixer = (
has_traj_mixer
or _TRAJ_MIXER_STATE_KEY.search(key) is not None
)
if has_ffn and has_traj_mixer:
raise ValueError(
"Checkpoint contains both Transformer FFN and TrajMixer "
"block parameters; its model architecture is ambiguous."
)
if has_ffn:
return TRANSFORMER_FFN_ARCHITECTURE
if has_traj_mixer:
return TRAJ_MIXER_ARCHITECTURE
raise ValueError(
"Could not detect model architecture from checkpoint parameters. "
"Expected a blocks.<index>.mlp FFN or TrajMixer parameter."
)
def resolve_model_architecture(
config_or_marker: Mapping[str, object] | str | None = None,
state_dict: Mapping[str, object] | None = None,
) -> str:
"""Resolve and cross-check a configured and checkpoint architecture.
Every saved run must provide an explicit architecture marker. Checkpoint
parameter names are used only to verify that the marker describes the
weights being loaded.
"""
if isinstance(config_or_marker, Mapping):
configured = config_or_marker.get("model_architecture")
elif isinstance(config_or_marker, str) or config_or_marker is None:
configured = config_or_marker
else:
raise TypeError(
"config_or_marker must be a config mapping, string, or None, got "
f"{type(config_or_marker).__name__}"
)
resolved_config = (
_validate_model_architecture(configured)
if configured is not None
else None
)
detected = (
detect_model_architecture_from_state_dict(state_dict)
if state_dict is not None
else None
)
if resolved_config is None:
raise ValueError(
"model_architecture is required; expected one of "
f"{SUPPORTED_MODEL_ARCHITECTURES}."
)
if detected is not None and resolved_config != detected:
raise ValueError(
"Configured model architecture conflicts with checkpoint: "
f"config={resolved_config!r}, checkpoint={detected!r}."
)
return resolved_config