"""Configuration for STRATA neural models.""" from __future__ import annotations import json from dataclasses import asdict, dataclass from pathlib import Path from typing import Any @dataclass(frozen=True, slots=True) class StrataConfig: """Plain-PyTorch decoder configuration. The config intentionally has no DeepSpeed, CPU offload, or distributed runtime knobs. Those concerns must stay outside the core model. """ vocab_size: int = 64_288 max_position_embeddings: int = 4096 d_model: int = 768 num_layers: int = 12 num_heads: int = 12 d_ff: int = 3072 dropout: float = 0.0 local_attention_window: int = 1024 predicate_block_every: int = 3 graph_relation_types: int = 16 node_type_vocab_size: int = 16 chart_type_vocab_size: int = 64 max_graph_edges_per_token: int = 8 use_attention_bias_from_graph: bool = True # Optional first-class graph-object memory path. Defaults off so existing # checkpoints instantiate the exact legacy architecture and load strictly. use_graph_object_memory: bool = False graph_object_relation_types: int = 70 graph_object_node_types: int = 20 graph_object_gate_init: float = -4.0 # v5 relation-conditioned message algebra. When enabled, graph-object edge # values are relation-conditioned messages over (src, dst, relation), rather # than generic src+dst+relation slots. graph_object_relation_conditioned_messages: bool = False # v7 predicate-slot memory. Relation labels select role-addressed predicate # slots: edge(src, relation, predicate) writes src into slot(predicate, # relation). This is the typed variable-binding path used to test whether # labels become operative rather than merely decodable edge features. graph_object_predicate_slot_memory: bool = False # When False (default), the node/chart/edge prediction heads are computed only # on the final predicate block (the ones the losses/eval consume). Set True to # restore the legacy behaviour where every predicate block emits them. emit_all_block_graph_heads: bool = False tie_word_embeddings: bool = True initializer_range: float = 0.02 pad_token_id: int = 0 bos_token_id: int = 1 eos_token_id: int = 2 def __post_init__(self) -> None: self.validate() def validate(self) -> None: if self.vocab_size <= 0: raise ValueError("vocab_size must be positive") if self.max_position_embeddings <= 0: raise ValueError("max_position_embeddings must be positive") if self.d_model <= 0: raise ValueError("d_model must be positive") if self.num_layers <= 0: raise ValueError("num_layers must be positive") if self.num_heads <= 0: raise ValueError("num_heads must be positive") if self.d_model % self.num_heads != 0: raise ValueError("d_model must be divisible by num_heads") if self.d_ff <= 0: raise ValueError("d_ff must be positive") if not 0.0 <= self.dropout < 1.0: raise ValueError("dropout must be in [0, 1)") if self.local_attention_window <= 0: raise ValueError("local_attention_window must be positive") if self.predicate_block_every <= 0: raise ValueError("predicate_block_every must be positive") if self.graph_relation_types <= 0: raise ValueError("graph_relation_types must be positive") if self.node_type_vocab_size <= 0: raise ValueError("node_type_vocab_size must be positive") if self.chart_type_vocab_size <= 0: raise ValueError("chart_type_vocab_size must be positive") if self.max_graph_edges_per_token <= 0: raise ValueError("max_graph_edges_per_token must be positive") if self.graph_object_relation_types <= 0: raise ValueError("graph_object_relation_types must be positive") if self.graph_object_node_types <= 0: raise ValueError("graph_object_node_types must be positive") @property def head_dim(self) -> int: return self.d_model // self.num_heads def to_dict(self) -> dict[str, Any]: return asdict(self) @classmethod def from_dict(cls, values: dict[str, Any]) -> "StrataConfig": return cls(**values) @classmethod def from_json_file(cls, path: str | Path) -> "StrataConfig": with Path(path).open("r", encoding="utf-8") as handle: payload = json.load(handle) if not isinstance(payload, dict): raise ValueError("config JSON must contain an object") return cls.from_dict(payload) def to_json_file(self, path: str | Path) -> None: destination = Path(path) destination.parent.mkdir(parents=True, exist_ok=True) with destination.open("w", encoding="utf-8") as handle: json.dump(self.to_dict(), handle, indent=2, sort_keys=True) handle.write("\n")