Skip to content

Commit 082b5a7

Browse files
committed
v58: add typed goal-gated structural evolution and compatible frozen layer growth
1 parent a9ea4e1 commit 082b5a7

11 files changed

Lines changed: 552 additions & 14 deletions

‎SYNTA_MORPHOLOGY.md‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Synta structural experiments, v58
2+
3+
The master can call `morph_model` to create a typed structural candidate, inspect
4+
`morph_model_status`, and choose an isolated CPU pilot (`test_submodel`) or queue
5+
an exclusive full-budget master experiment (`propose_scratch_master`). These are
6+
executable tools, not requests to manually paste Python. Existing mission
7+
scheduling consumes the queued master experiments.
8+
9+
The bounded search space includes GRU or causal Transformer, width 16–256,
10+
1–4 backbone layers, compatible attention heads, embedding width, and 0–4
11+
residual bottleneck layers. The conservative pilot ceiling is two million
12+
parameters. Vocabulary IDs remain the same 257 UTF-8 byte symbols. Changing
13+
width/depth/backbone creates an independently trained model; it does not reshape
14+
Gemma GGUF tensors in place.
15+
16+
Warm growth requires a trained typed predecessor with the same goal and backbone
17+
shape. Added residual layers initially contribute zero. Existing parameters are
18+
frozen; only new layers learn. Parent weights and source are hashed, retained and
19+
checked before reuse. Separate safetensors checkpoints permit rollback.
20+
21+
Master replacement requires verified source-held-out goal outcomes before
22+
training, strict improvement over the parent on that task, performance above the
23+
best constant-label baseline, complete retained capability tests, fresh audit,
24+
and official benchmark gates when configured. The current master remains active
25+
when data or evidence is missing or a gate fails. An improved tiny specialist is
26+
not automatically a better general master.
27+
28+
Pilot and full-master budgets differ. A completed pilot cannot be silently
29+
relabelled as a full-budget master trial. Create a fresh candidate for the latter.
30+
No architecture, throughput, profit or zero-forgetting guarantee is made.
31+
GPU experiments use the existing isolated exclusive PyTorch path; Windows RAM
32+
and VRAM are not pooled. Production hardware execution remains to be checked on
33+
the operator's machines.

‎SYNTA_REQUIREMENTS_AUDIT.md‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Synta: requirement and verification audit, v57
1+
# Synta: requirement and verification audit, v58
22

33
This audits the visible conversation. "Implemented" means code exists and the
44
listed checks pass, not that it has run successfully on the operator's computers.
@@ -55,3 +55,11 @@ Verification result: full test suite 1028 passed, 65 skipped, no deselections;
5555
ruff and upgrade shell checks pass. Windows startup policy has static contract
5656
checks, not a Windows runtime execution. Real CPU child-training/tensor-validation
5757
integration is included with simulated resource readings.
58+
59+
## v58 structural evolution
60+
61+
`morphology.py` and named research tools add bounded typed GRU/Transformer shape
62+
proposals and compatible identity-initialized residual growth. Existing queued
63+
master trials execute them in isolation. Activation additionally requires verified
64+
goal outcomes and strict measured task improvement. Native GGUF architecture is
65+
not reshaped in place. See `SYNTA_MORPHOLOGY.md` for the executable contract.

‎pyproject.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name = "rlms"
33
authors = [
44
{name = "Alex Zhang", email = "altzhang@mit.edu"},
55
]
6-
version = "0.1.3+v100.57"
6+
version = "0.1.3+v100.58"
77
description = "Recursive Language Models."
88
readme = "README.md"
99
license = "MIT"

‎rlm/v100/architecture_promotion.py‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,14 @@ def activate(root: Path, parent: dict, result: dict, gates: list[dict]) -> tuple
4949
expert = registry.get(result["expert_id"])
5050
candidate = copy.deepcopy(expert["profile"])
5151
quality = expert["quality_reports"]["candidate"]
52+
requires_goal = candidate.get("resources", {}).get("require_goal_improvement", False)
53+
if requires_goal and not result.get("goal_gate"):
54+
raise ValueError("Morphology activation needs measured goal improvement")
5255
if result.get("goal_gate"):
5356
from rlm.v100.architecture_goal_gate import verify
5457

55-
if not verify(root, Path(result["goal_gate"]), parent, candidate)["passed"]:
58+
proof = verify(root, Path(result["goal_gate"]), parent, candidate)
59+
if not proof["passed"] or (requires_goal and not proof.get("improvements")):
5660
raise ValueError("Architecture failed its goal-linked development gate")
5761
if (
5862
not complete(quality)

‎rlm/v100/architecture_worker.py‎

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,17 +55,24 @@ def main() -> None:
5555
if not 1 <= parameters <= config["max_parameters"]:
5656
raise ValueError("Architecture exceeds its parameter budget")
5757
model.to(device)
58-
if config.get("init_weights"):
58+
if mode == "train" and config.get("init_weights"):
5959
initial = Path(config["init_weights"])
6060
if hashlib.sha256(initial.read_bytes()).hexdigest() != config["init_weights_sha256"]:
6161
raise ValueError("Scratch continuation weights changed")
62-
model.load_state_dict(load_file(str(initial)), strict=True)
62+
state = load_file(str(initial))
63+
if config.get("morph_growth"):
64+
model.load_parent_state(state)
65+
else:
66+
model.load_state_dict(state, strict=True)
6367
# All parameters of this new network may learn; the Gemma parent is absent.
64-
model.requires_grad_(True)
68+
if not config.get("morph_growth"):
69+
model.requires_grad_(True)
6570
if mode == "train":
6671
records = json.loads(Path(data_path).read_text())
6772
examples = [encoded(row, config["context_window"]) for row in records]
68-
optimizer = torch.optim.AdamW(model.parameters(), lr=config["learning_rate"])
73+
optimizer = torch.optim.AdamW(
74+
[p for p in model.parameters() if p.requires_grad], lr=config["learning_rate"]
75+
)
6976
scaler = torch.amp.GradScaler("cuda", enabled=device == "cuda", init_scale=128)
7077
validation = []
7178
if config.get("validation_file"):

‎rlm/v100/architectures.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,9 +365,12 @@ def run_candidate(
365365
suite: Path,
366366
budget: dict | None = None,
367367
init_weights: Path | None = None,
368+
morph_growth: bool = False,
368369
) -> dict:
369370
from rlm.v100.training import load_records
370371

372+
if type(morph_growth) is not bool or (morph_growth and init_weights is None):
373+
raise ValueError("Invalid morphology initialization")
371374
budget = copy.deepcopy(budget or DEFAULT_BUDGET)
372375
validate_budget(budget)
373376
output = candidate_path(root, candidate_id)
@@ -418,6 +421,7 @@ def run_candidate(
418421
if init_weights is not None:
419422
if init_weights.is_symlink() or not init_weights.is_file():
420423
raise ValueError("Continuation weights must be an ordinary frozen file")
424+
configuration["morph_growth"] = morph_growth
421425
configuration["init_weights"] = str(init_weights.resolve())
422426
configuration["init_weights_sha256"] = file_hash(init_weights)
423427
mounts = [(init_weights.resolve(), False)]

‎rlm/v100/morphology.py‎

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
"""Typed structural proposals for isolated goal-gated master candidates.
2+
3+
No source is imported on the controller. Native GGUF weights are never relabeled
4+
as another architecture. Warm growth is limited to compatible typed predecessors.
5+
"""
6+
7+
import ast
8+
import json
9+
from pathlib import Path
10+
11+
from rlm.v100.activity import ActivityLog
12+
from rlm.v100.architectures import candidate_path, create_candidate, verify_candidate
13+
from rlm.v100.goals import load_goal
14+
from rlm.v100.protection import file_hash
15+
16+
MODEL_SOURCE = """
17+
import torch
18+
from torch import nn
19+
20+
class MorphModel(nn.Module):
21+
def __init__(self, config):
22+
super().__init__()
23+
s = MORPH_SHAPE
24+
w = s['width']
25+
self.embedding = nn.Embedding(257, w)
26+
if s['architecture'] == 'gru':
27+
self.body = nn.GRU(w, w, s['layers'], batch_first=True)
28+
else:
29+
self.body = nn.TransformerEncoder(
30+
nn.TransformerEncoderLayer(w, s['heads'], w * 2, dropout=0, batch_first=True),
31+
s['layers'], enable_nested_tensor=False)
32+
self.head = nn.Linear(w, 257)
33+
self.residual = nn.ModuleList([
34+
nn.Sequential(nn.Linear(257, s['bottleneck']), nn.GELU(), nn.Linear(s['bottleneck'], 257))
35+
for _ in range(s['residual_layers'])])
36+
for block in self.residual:
37+
nn.init.zeros_(block[-1].weight)
38+
nn.init.zeros_(block[-1].bias)
39+
40+
def load_parent_state(self, state):
41+
old = MORPH_SHAPE['parent_shape']
42+
if old is None:
43+
raise ValueError('No compatible morphology predecessor')
44+
expected = self.state_dict()
45+
old_blocks = old['residual_layers']
46+
allowed_missing = {k for k in expected if k.startswith('residual.') and int(k.split('.')[1]) >= old_blocks}
47+
if set(state) != set(expected) - allowed_missing:
48+
raise ValueError('Parent keys differ from the frozen growth contract')
49+
if any(expected[k].shape != value.shape for k, value in state.items()):
50+
raise ValueError('Parent shape differs from the frozen growth contract')
51+
self.load_state_dict(state, strict=False)
52+
self.requires_grad_(False)
53+
for block in list(self.residual)[old_blocks:]:
54+
block.requires_grad_(True)
55+
self.embedding.eval()
56+
self.body.eval()
57+
self.head.eval()
58+
59+
def forward(self, tokens):
60+
value = self.embedding(tokens)
61+
if MORPH_SHAPE['architecture'] == 'gru':
62+
value, _ = self.body(value)
63+
else:
64+
# Parameter-free positions preserve the vocabulary and avoid a huge
65+
# learned positional table when the parent has a long context.
66+
positions = torch.arange(tokens.shape[1], device=tokens.device).float()
67+
scale = torch.arange(value.shape[-1], device=tokens.device).float() + 1
68+
value = value + torch.sin(positions[:, None] / scale[None, :]).to(value.dtype)
69+
mask = torch.triu(torch.ones(tokens.shape[1], tokens.shape[1], device=tokens.device, dtype=torch.bool), 1)
70+
value = self.body(value, mask=mask)
71+
value = self.head(value)
72+
for block in self.residual:
73+
value = value + block(value)
74+
return value
75+
76+
def build(config):
77+
return MorphModel(config)
78+
"""
79+
80+
81+
def read_shape(source: Path) -> dict | None:
82+
if source.is_symlink() or source.stat().st_size > 12000:
83+
raise ValueError("Morphology source exceeds its frozen budget")
84+
for node in ast.parse(source.read_text()).body:
85+
if isinstance(node, ast.Assign) and any(
86+
isinstance(t, ast.Name) and t.id == "MORPH_SHAPE" for t in node.targets
87+
):
88+
value = ast.literal_eval(node.value)
89+
if not isinstance(value, dict) or value.get("schema") != "synta-morphology-v1":
90+
raise ValueError("Invalid morphology shape contract")
91+
return value
92+
return None
93+
94+
95+
def propose(
96+
root: Path,
97+
branch: str,
98+
candidate_id: str,
99+
architecture: str,
100+
width: int,
101+
layers: int,
102+
heads: int,
103+
residual_layers: int,
104+
bottleneck: int,
105+
parent_candidate_id: str,
106+
hypothesis: str,
107+
) -> dict:
108+
goal = load_goal(root)
109+
if not goal:
110+
raise ValueError("Morphology needs the recorded operator goal")
111+
if architecture not in ("gru", "transformer") or any(
112+
type(v) is not int for v in (width, layers, heads, residual_layers, bottleneck)
113+
):
114+
raise ValueError("Invalid morphology dimensions")
115+
if not (
116+
16 <= width <= 256
117+
and 1 <= layers <= 4
118+
and heads in (1, 2, 4, 8)
119+
and width % heads == 0
120+
and 0 <= residual_layers <= 4
121+
and 1 <= bottleneck <= 256
122+
):
123+
raise ValueError("Morphology dimensions exceed the bounded search space")
124+
# Conservative bound, before allocating a tensor on any worker.
125+
estimate = (
126+
257 * width * 2
127+
+ layers * width * width * 12
128+
+ residual_layers * (514 * bottleneck + 257 + bottleneck)
129+
)
130+
if estimate > 2_000_000:
131+
raise ValueError("Morphology exceeds two million pilot parameters")
132+
parent_shape, parent_weights_hash = None, None
133+
if parent_candidate_id:
134+
parent = candidate_path(root, parent_candidate_id)
135+
manifest = verify_candidate(parent)
136+
parent_shape = read_shape(parent / "source/model.py")
137+
if manifest["goal"] != goal or parent_shape is None:
138+
raise ValueError("Warm growth requires a typed predecessor for the same goal")
139+
if (
140+
any(
141+
parent_shape[key] != value
142+
for key, value in {
143+
"architecture": architecture,
144+
"width": width,
145+
"layers": layers,
146+
"heads": heads,
147+
"bottleneck": bottleneck,
148+
}.items()
149+
)
150+
or residual_layers <= parent_shape["residual_layers"]
151+
):
152+
raise ValueError(
153+
"Warm growth can append residual layers; other shape changes need an independent candidate"
154+
)
155+
weights = parent / "trial/weights/weights.safetensors"
156+
if weights.is_symlink() or not weights.is_file() or weights.stat().st_size > 16 * 2**20:
157+
raise ValueError("Warm growth requires bounded trained predecessor weights")
158+
parent_weights_hash = file_hash(weights)
159+
parent_shape = {key: value for key, value in parent_shape.items() if key != "parent_shape"}
160+
shape = {
161+
"schema": "synta-morphology-v1",
162+
"goal_id": goal["id"],
163+
"architecture": architecture,
164+
"width": width,
165+
"layers": layers,
166+
"heads": heads,
167+
"residual_layers": residual_layers,
168+
"bottleneck": bottleneck,
169+
"parent_candidate_id": parent_candidate_id,
170+
"parent_weights_sha256": parent_weights_hash,
171+
"parent_shape": parent_shape,
172+
}
173+
result = create_candidate(
174+
root, branch, candidate_id, "MORPH_SHAPE = " + repr(shape) + "\n" + MODEL_SOURCE, hypothesis
175+
)
176+
result.update(
177+
shape=shape,
178+
estimated_parameters=estimate,
179+
weights_changed=False,
180+
next_tools=["test_submodel", "propose_scratch_master"],
181+
scope="Isolated structural candidate; master activation requires strict measured goal improvement and all retained/fresh/public gates",
182+
)
183+
ActivityLog(root, branch, "morphology").write("decisions", "morphology-proposed", result)
184+
return result
185+
186+
187+
def initialization(root: Path, candidate_id: str) -> tuple[Path | None, bool]:
188+
folder = candidate_path(root, candidate_id)
189+
verify_candidate(folder)
190+
shape = read_shape(folder / "source/model.py")
191+
if shape is None or not shape["parent_candidate_id"]:
192+
return None, False
193+
parent = candidate_path(root, shape["parent_candidate_id"])
194+
verify_candidate(parent)
195+
weights = parent / "trial/weights/weights.safetensors"
196+
if weights.is_symlink() or file_hash(weights) != shape["parent_weights_sha256"]:
197+
raise ValueError("Morphology predecessor weights changed")
198+
return weights, True
199+
200+
201+
def status(root: Path) -> dict:
202+
values = []
203+
for folder in sorted((root / "research/architecture-candidates").glob("*"))[-64:]:
204+
verify_candidate(folder)
205+
shape = read_shape(folder / "source/model.py")
206+
if shape is not None:
207+
report = folder / "trial/quality.json"
208+
values.append(
209+
{
210+
"candidate_id": folder.name,
211+
"shape": shape,
212+
"trial": json.loads(report.read_text()) if report.exists() else None,
213+
}
214+
)
215+
return {
216+
"candidates": values,
217+
"scope": "Shape proposals and tested artifacts; not deployment claims",
218+
}

0 commit comments

Comments
 (0)