Skip to content

Commit cbd7f21

Browse files
committed
v88: recover stopped missions independently and improve owned GPU throughput
1 parent a840170 commit cbd7f21

23 files changed

Lines changed: 862 additions & 90 deletions

‎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.87"
6+
version = "0.1.3+v100.88"
77
description = "Recursive Language Models."
88
readme = "README.md"
99
license = "MIT"

‎rlm/v100/drones.py‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,12 +311,23 @@ def finish(root: Path, identity: str, value: dict) -> None:
311311
)
312312

313313

314+
def inference_lane(root: Path, kind: str) -> str:
315+
if kind == "income":
316+
return "accepted-master"
317+
if gpu_disabled(root):
318+
return "accepted-master"
319+
if kind != "critic" and selected_node(root) is not None:
320+
return "debian-3060ti"
321+
return "windows-helper"
322+
323+
314324
def service(root: Path, stop: threading.Event) -> None:
315325
# One model job plus up to four CPU/network jobs and one observer. RTX requests share the global
316326
# existing helper pacing lock with foreground researchers and benchmark clients.
317327
with ThreadPoolExecutor(max_workers=6) as workers:
318328
active = {}
319329
affinities = {}
330+
lanes = {}
320331
goal_future = None
321332
goal_due = 0
322333
while not stop.is_set():
@@ -354,6 +365,7 @@ def service(root: Path, stop: threading.Event) -> None:
354365
finish(root, identity, result)
355366
del active[identity]
356367
affinities.pop(identity, None)
368+
lanes.pop(identity, None)
357369
with connect(root) as db:
358370
from rlm.v100.resource_budget import local_budget
359371

@@ -373,6 +385,9 @@ def service(root: Path, stop: threading.Event) -> None:
373385
)
374386
if used >= (llm_slots if llm else budget["cpu_slots"]):
375387
continue
388+
lane = inference_lane(root, row["kind"]) if llm else None
389+
if lane is not None and lane in lanes.values():
390+
continue
376391
job = dict(row)
377392
if job["kind"] == "python":
378393
from rlm.v100.resource_budget import cpu_assignment
@@ -388,6 +403,8 @@ def service(root: Path, stop: threading.Event) -> None:
388403
if updated.rowcount:
389404
if job["kind"] == "python":
390405
affinities[row["id"]] = assignment
406+
if lane is not None:
407+
lanes[row["id"]] = lane
391408
active[row["id"]] = (workers.submit(execute, root, job), row["kind"])
392409
atomic_json(
393410
root / "research/drones-status.json",
@@ -398,6 +415,7 @@ def service(root: Path, stop: threading.Event) -> None:
398415
"cpu_slots": budget["cpu_slots"],
399416
"budget": budget,
400417
"cpu_affinities": affinities,
418+
"inference_lanes": lanes,
401419
"rtx_slots": 0
402420
if gpu_disabled(root)
403421
else 1 + int(selected_node(root) is not None),

‎rlm/v100/live_status.py‎

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,8 @@ def snapshot(root: Path, mission: dict, report: dict, gpu: dict) -> dict:
369369
from rlm.v100.common import load_profile
370370
from rlm.v100.competition import helper_client
371371
from rlm.v100.mission_chat import preferences
372-
from rlm.v100.remote_helper import remote_profile, selected_helper
372+
from rlm.v100.model_roles import selected_profile
373+
from rlm.v100.remote_helper import remote_profile
373374

374375
enabled = preferences(root).get("remote_helper_enabled", True) is not False
375376
if not enabled:
@@ -384,11 +385,23 @@ def snapshot(root: Path, mission: dict, report: dict, gpu: dict) -> dict:
384385
try:
385386
if not enabled:
386387
raise RuntimeError("Operator disabled helper probes")
387-
profile = load_profile(selected_helper(root), root)
388+
profile = load_profile(
389+
selected_profile(root, "planner") or root / "research/researcher-rtx3090.json", root
390+
)
388391
if not remote_profile(profile):
389392
raise ValueError("Remote helper is not configured")
390393
client = helper_client(profile, root)
391394
client.timeout = 3
395+
guard = client.remote_request("/api/synta-resources")
396+
age = time.time() - guard.get("updated", 0)
397+
if guard.get("contract") != "synta-adaptive-windows-v1" or not 0 <= age <= 10:
398+
raise RuntimeError("Windows resource monitor heartbeat stale or unavailable")
399+
rtx["metrics"] = guard
400+
rtx["detail"] = (
401+
guard.get("reason", "Fresh Windows resource sample")
402+
+ "; active inference: "
403+
+ str(guard.get("generating", False))
404+
)
392405
info = client.remote_request("/api/ps")
393406
loaded = [
394407
model for model in info.get("models", []) if model.get("digest") == client.model_digest
@@ -447,7 +460,7 @@ def snapshot(root: Path, mission: dict, report: dict, gpu: dict) -> dict:
447460
else ""
448461
),
449462
"metrics": value.get("resources", {}),
450-
"stale": age > 90,
463+
"stale": age > 90 or value.get("phase") == "unavailable",
451464
"model": value.get("model"),
452465
"receipt": str(owned),
453466
"jobs": [],
@@ -468,6 +481,7 @@ def snapshot(root: Path, mission: dict, report: dict, gpu: dict) -> dict:
468481
("knowledge", "Wikipedia / RAG", "research/knowledge/import-status.json"),
469482
("knowledge-index", "Live Wikipedia index", "research/knowledge/index-progress.json"),
470483
("managed-update", "Automatic GitHub updates", "research/managed-update/status.json"),
484+
("mission-watchdog", "Mission recovery", "research/mission-watchdog/status.json"),
471485
("model-roles", "Qualified model roles", "research/model-roles/active.json"),
472486
(
473487
"native-qualification",

‎rlm/v100/managed_bootstrap.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ def materialize(folder: Path, revision: str, manifest: dict) -> None:
7272

7373
def linux(root: Path, folder: Path, revision: str, manifest: dict) -> dict:
7474
python = root / "venvs/v100-continual/bin/python"
75+
watchdog = folder / "rlm/v100/mission_watchdog.py"
76+
if watchdog.is_file():
77+
subprocess.run([str(python), str(watchdog), "--root", str(root)], check=True, timeout=120)
7578
processes = subprocess.check_output(["ps", "-eo", "args="], text=True)
7679
exclusive = any(
7780
"rlm.v100.cli" in line

‎rlm/v100/managed_prepare.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ def run(root: Path) -> dict:
102102
if profile.exists():
103103
value = json.loads(profile.read_text())
104104
value["runtime"]["base_url"] = endpoint
105+
value["resources"]["require_adaptive_windows_guard"] = True
105106
# This changes only transport to the explicitly owned peer; pinned model/template remains.
106107
atomic_json(profile, value)
107108
try:

‎rlm/v100/mission_watchdog.py‎

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
"""Independent availability recovery, without interrupting training or operator pauses."""
2+
3+
import argparse
4+
import fcntl
5+
import json
6+
import shlex
7+
import subprocess
8+
import time
9+
from pathlib import Path
10+
11+
from rlm.v100.common import atomic_json, load_profile
12+
from rlm.v100.mission import status
13+
from rlm.v100.supervisor import install
14+
15+
EXCLUSIVE = {"train", "mission-prepare", "optimize-mtp", "calibrate-training", "test-mtp"}
16+
17+
18+
def exclusive_work(root: Path) -> bool:
19+
rows = subprocess.check_output(["ps", "-eo", "args="], text=True, timeout=10)
20+
for row in rows.splitlines():
21+
if "rlm.v100.cli" not in row:
22+
continue
23+
arguments = shlex.split(row)
24+
if "rlm.v100.cli" in arguments and arguments.count("--root") == 1:
25+
index = arguments.index("--root") + 1
26+
if index < len(arguments) and Path(arguments[index]).resolve() == root.resolve():
27+
if EXCLUSIVE.intersection(arguments):
28+
return True
29+
return False
30+
31+
32+
def preserved_profile(root: Path, record: dict) -> Path:
33+
candidates = []
34+
if record.get("learning", {}).get("live_profile"):
35+
candidates.append(Path(record["learning"]["live_profile"]))
36+
if record.get("run"):
37+
candidates.append(Path(record["run"]) / "input-profile.json")
38+
checkpoint = root / "research/availability-recovery-update/current.json"
39+
if checkpoint.is_file():
40+
candidates.append(Path(json.loads(checkpoint.read_text())["profile"]))
41+
errors = []
42+
for path in candidates:
43+
path = path.resolve()
44+
if path.is_relative_to(root / "research") and path.is_file():
45+
try:
46+
load_profile(path, root)
47+
return path
48+
except (ValueError, OSError, KeyError) as error:
49+
errors.append(str(error)[:200])
50+
raise ValueError("No valid preserved accepted profile: " + "; ".join(errors))
51+
52+
53+
def recover(root: Path, resume: bool = False) -> dict:
54+
root = root.resolve()
55+
folder = root / "research/mission-watchdog"
56+
folder.mkdir(parents=True, exist_ok=True)
57+
receipt = folder / "status.json"
58+
request = folder / "resume-request.json"
59+
if resume:
60+
atomic_json(request, {"requested": time.time()})
61+
resume = resume or request.exists()
62+
with (
63+
(folder / "lease.lock").open("a") as lease,
64+
(root / "research/mission-maintenance.lock").open("a") as maintenance,
65+
):
66+
try:
67+
fcntl.flock(lease, fcntl.LOCK_EX | fcntl.LOCK_NB)
68+
fcntl.flock(maintenance, fcntl.LOCK_SH | fcntl.LOCK_NB)
69+
except BlockingIOError:
70+
return {"stage": "maintenance-in-progress", "updated": time.time()}
71+
try:
72+
record = status(root)
73+
pause = root / "research/supervisor/pause.json"
74+
if resume:
75+
pause.unlink(missing_ok=True)
76+
if pause.exists():
77+
result = {"stage": "operator-paused"}
78+
elif record.get("running"):
79+
request.unlink(missing_ok=True)
80+
result = {"stage": "mission-running", "run": record.get("run")}
81+
elif exclusive_work(root):
82+
result = {
83+
"stage": "deferred-training",
84+
"reason": "Owned exclusive job remains active",
85+
}
86+
else:
87+
previous = json.loads(receipt.read_text()) if receipt.is_file() else {}
88+
if not resume and time.time() < previous.get("next_retry", 0):
89+
result = {**previous, "stage": "recovery-backoff"}
90+
else:
91+
supervisor = root / "research/supervisor/status.json"
92+
state = json.loads(supervisor.read_text()) if supervisor.is_file() else {}
93+
if state.get("running") and 0 <= time.time() - state.get("updated", 0) < 15:
94+
result = {"stage": "supervisor-recovering", "supervisor": state}
95+
else:
96+
profile = preserved_profile(root, record)
97+
# Recheck after profile validation; another owned process may have started.
98+
if status(root).get("running") or exclusive_work(root):
99+
result = {"stage": "work-started-during-check"}
100+
else:
101+
result = {
102+
"stage": "recovery-submitted",
103+
"profile": str(profile),
104+
"supervisor": install(root, profile),
105+
"next_retry": time.time() + 300,
106+
}
107+
except (OSError, ValueError, KeyError, RuntimeError, subprocess.SubprocessError) as error:
108+
result = {
109+
"stage": "recovery-blocked",
110+
"error": str(error)[:1000],
111+
"next_retry": time.time() + 300,
112+
}
113+
result["updated"] = time.time()
114+
atomic_json(receipt, result)
115+
return result
116+
117+
118+
def main() -> None:
119+
parser = argparse.ArgumentParser()
120+
parser.add_argument("--root", type=Path, required=True)
121+
parser.add_argument("--resume", action="store_true")
122+
args = parser.parse_args()
123+
print(json.dumps(recover(args.root, args.resume)), flush=True)
124+
125+
126+
if __name__ == "__main__":
127+
main()

‎rlm/v100/model_roles.py‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,26 @@ def qualify(root: Path) -> dict:
175175
baseline_profile = load_profile(original, root)
176176
if baseline_profile["runtime"]["model_name"] != MODEL:
177177
raise ValueError("Role qualification requires the preserved pinned 9B baseline")
178+
inventory = helper_client(baseline_profile, root).remote_request("/api/tags")
179+
fingerprint = json_hash(
180+
{
181+
"profile": baseline_profile,
182+
"models": sorted((m["name"], m["digest"]) for m in inventory["models"]),
183+
"panel": cases(),
184+
"revision": "roles-v88",
185+
}
186+
)
187+
cache = root / "research/model-roles/qualification-cache.json"
188+
active = root / "research/model-roles/active.json"
189+
if cache.is_file() and active.is_file():
190+
prior_cache = json.loads(cache.read_text())
191+
if (
192+
prior_cache.get("fingerprint") == fingerprint
193+
and time.time() - prior_cache.get("updated", 0) < 21600
194+
):
195+
selected_profile(root, "fast")
196+
selected_profile(root, "planner")
197+
return json.loads(active.read_text())
178198
folder = root / "research/model-roles" / str(time.time_ns())
179199
folder.mkdir(parents=True)
180200
production_output = baseline_profile["runtime"]["max_output_tokens"]
@@ -272,6 +292,11 @@ def qualify(root: Path) -> dict:
272292
scope="Admitted role profiles only; Gemma learner and accepted weights retained",
273293
)
274294
atomic_json(active, prior)
295+
# Transport failures retry; stable scored outcomes need no repeated GPU exercise.
296+
if all(
297+
"error" not in r or r["error"] == "Candidate not downloaded on Windows" for r in reports
298+
):
299+
atomic_json(cache, {"fingerprint": fingerprint, "updated": time.time()})
275300
return prior
276301

277302

‎rlm/v100/remote_helper.py‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,8 @@ def workload_slot(self, wait: bool = True):
290290
yield quota
291291

292292
def reserve_helper_idle(self, quota: dict, request_seconds: float) -> None:
293-
idle = request_seconds * (100 - self.helper_duty_percent) / self.helper_duty_percent
293+
duty = quota.get("adaptive_duty_percent", self.helper_duty_percent)
294+
idle = request_seconds * (100 - duty) / duty
294295
state = {"boot_id": quota["boot_id"], "not_before": time.monotonic() + idle}
295296
if quota["path"] is not None:
296297
atomic_json(quota["path"], state)
@@ -306,7 +307,7 @@ def reserve_helper_idle(self, quota: dict, request_seconds: float) -> None:
306307
{
307308
"endpoint": self.base_url,
308309
"batch_tokens": self.helper_batch_tokens,
309-
"active_wall_time_target_percent": self.helper_duty_percent,
310+
"active_wall_time_target_percent": duty,
310311
"request_seconds": request_seconds,
311312
"planned_idle_seconds_before_next_turn": idle,
312313
"waited_seconds_before_this_turn": quota["waited_seconds"],
@@ -486,6 +487,13 @@ def http_request(self, endpoint: str, data: dict | None = None) -> dict:
486487
+ " adaptive guard unavailable: "
487488
+ str(guard.get("reason", "unknown"))
488489
)
490+
if guard.get("throughput_enabled") is True and not self.dedicated_host:
491+
duty = guard.get("duty_percent")
492+
if type(duty) is int and 1 <= duty <= 100:
493+
quota["adaptive_duty_percent"] = duty
494+
batch = guard.get("batch_tokens")
495+
if type(batch) is int and batch in (16, 32, 64, 128, 256):
496+
payload["options"]["num_batch"] = batch
489497
self.identity()
490498
if not self.on_demand:
491499
self.loaded()

0 commit comments

Comments
 (0)