Skip to content

Commit 239c76d

Browse files
committed
v39: admit accepted-master CPU chat during GPU training and refresh private desktop sources
1 parent 98e5d15 commit 239c76d

11 files changed

Lines changed: 651 additions & 36 deletions

‎V100_AUDIT.md‎

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# V100 mission audit v100.38
1+
# V100 mission audit v100.39
22

33
This inventory separates source implementation from configuration, execution and
44
demonstrated improvement. Passing controller tests does not certify GPU stability,
@@ -12,7 +12,7 @@ every requested feature is complete or optimal.
1212
| Autonomous self-upgrade loop | `mission.py`, `continuous.py`, `supervisor.py` | Accepted updates and numerical health, not loop count alone |
1313
| Operator-owned long-term objective | `goals.py`, `chat_goals.py`, `planning.py` | Only a clear current operator request can change it; upgrades preserve old goals |
1414
| Short/mid-term plans from ordinary chat | `mission_chat.py`, `planning.py` | Versioned host receipts; a written plan does not mean its experiment ran |
15-
| Chat while research continues | Persistent queue, accepted master and explicit RTX delegate | One V100 slot shares inference; exclusive training prevents accepted-master chat temporarily |
15+
| Chat while research continues | Persistent queue, accepted native master on GPU or RAM-admitted CPU, explicit RTX delegate | CPU copy uses the same accepted weights, is slower and may be deferred by host RAM pressure |
1616
| Factual status in chat | `mission_evidence.py`, supplied automatically and as a research tool | Report paths, successful optimizer counters and accepted versions are distinct; missing counters mean unknown |
1717
| Long context, Flash Attention, KV | Native configuration and fit checks | Device execution and long-input accuracy; free VRAM alone does not establish speed |
1818
| Adaptive thinking/output | Validated `research_policy.py` budgets | Research only; fixed evaluations retain comparable settings |
@@ -37,6 +37,36 @@ every requested feature is complete or optimal.
3737
| Learn from profitable/losing outcomes | Audited paper accounting, causal DPO, shadow reward head | Resolved valid outcomes required; not unbiased profit-maximizing policy RL |
3838
| Net results and local alerts | Audited paper reports and labeled rejected/accepted proposals | No real orders or demonstrated income edge inferred from signals |
3939

40+
## v100.39 additions
41+
42+
`chat_backend.py` can serve the identical accepted native GGUF on a separate
43+
CPU-only loopback endpoint while the V100 performs an exclusive training or
44+
candidate-evaluation job. It never adopts the model found on the candidate endpoint.
45+
The model digest and native launch receipt are checked, including every cached
46+
reuse. Two low-priority threads, an 8192-token context, 1024 output tokens and
47+
no draft GPU model bound this fallback. Admission requires at least 16 GiB
48+
available host RAM, or a larger weight-size-dependent estimate. An owned-process
49+
watchdog retires it below 4 GiB available RAM, above its RSS budget, or on mission
50+
shutdown. Idle copies close after 180 seconds. A labelled RTX delegate remains
51+
the fallback if CPU admission fails; otherwise requests stay queued during outages.
52+
This adds concurrency through CPU serving, not simultaneous training/inference on
53+
the single GPU or a guarantee of low chat latency. Actual device fit/speed remains
54+
an on-device check. `accepted-cpu-chat-status.json` and chat responder receipts
55+
identify the backend and accepted model digest.
56+
57+
Stopped campaign setup refreshes the guest's readonly code ISO to the newly
58+
pinned own-source revision instead of retaining the original v32 code indefinitely.
59+
It preserves the writable guest disk, refuses a live-guest replacement, checks the
60+
ISO digest and leaves the old ISO untouched if building the replacement fails.
61+
GUI health now reports separate cloud-init, workspace, source, service and display
62+
checks, bounded guest installation/service logs, and loaded versus expected source
63+
revision. A stale source mount cannot report full readiness. This diagnoses startup
64+
failures; it does not certify that this user's VM has completed installation.
65+
66+
Campaign upgrades preserve the previous mission input profile even before its
67+
first completed learning cycle. Validated learning checkpoints still take priority.
68+
The user-owned goals and prior accepted weights are not reset by this addition.
69+
4070
## v100.38 additions
4171

4272
Campaign setup automatically creates a separate public benchmark snapshot when

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

‎rlm/v100/campaign.py‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ def choose_profile(root: Path) -> Path:
5151
if live and Path(live).exists():
5252
# Serving checkpoints take precedence over newly generated speed-test profiles.
5353
return Path(live)
54+
if previous.get("run"):
55+
input_profile = Path(previous["run"]) / "input-profile.json"
56+
if input_profile.is_file():
57+
return input_profile
5458
for path in (root / "research/logs").glob("mtp-ab-*/validated-profile.json"):
5559
if valid(load_profile(path, root)):
5660
candidates.append(path)
@@ -165,6 +169,7 @@ def stage(name, function):
165169
resident_drones=True,
166170
independent_branch_serving=True,
167171
interactive_lab=True,
172+
accepted_cpu_chat=True,
168173
public_benchmarks=bool(current(root)),
169174
mission_max_context=131072,
170175
)

‎rlm/v100/capabilities.py‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ def audit(root: Path) -> dict:
3434
"audited_paper_outcomes": evidence["available_audited_paper_outcomes"],
3535
"research_policy": settings(root),
3636
"user_controls": {
37-
"chat": "persistent queue; accepted V100 while available, explicitly labeled RTX delegate during exclusive training",
37+
"chat": "persistent queue; accepted V100, RAM-admitted identical accepted weights on CPU during GPU training, then explicit RTX delegate",
3838
"long_term_goal": "clear current operator natural-language request or /goal or /cel; no autonomous model changes",
3939
"short_mid_plans": "versioned plans, model or operator can revise",
4040
"directives": "next R&D request",
@@ -51,6 +51,7 @@ def audit(root: Path) -> dict:
5151
"hybrid_memory_prepared": (root / "research/mission-semantic.json").exists(),
5252
"own_code_prepared": (root / "research/self-code-source.json").exists(),
5353
"desktop_prepared": (root / "research/desktop/manifest.json").exists(),
54+
"accepted_cpu_chat_enabled": profile.get("resources", {}).get("accepted_cpu_chat", False),
5455
"public_benchmarks_prepared": (root / "research/public-benchmarks/current.json").exists(),
5556
"resident_workers": evidence["drones"],
5657
"implemented": "LoRA SFT and outcome-derived DPO preferences, optimizer checkpoints, replay/KL, fixed/public plus one-use fresh gates, new verified curriculum tool, bounded reward shadow head, automatic library-supported or isolated custom full-weight master architecture activation/rollback, owned distributed CPU training workers, source drones and sandbox/guest browser code candidates",
@@ -90,7 +91,7 @@ def audit(root: Path) -> dict:
9091
"not_implemented": [
9192
"profit-maximizing policy RL",
9293
"hard per-helper GPU utilization or power cap on Windows WDDM",
93-
"accepted-master chat inference during exclusive master training on the single V100; RTX delegation is used instead",
94+
"simultaneous GPU training and GPU accepted-master chat on the single V100; concurrent accepted-master CPU chat is RAM-admitted",
9495
"autonomous distributed free managed Colab workers",
9596
"automatic certification of sports/equities provider fees, account rights and settlement semantics",
9697
"automatic account creation or bypassing browser authentication/quotas",

‎rlm/v100/chat_backend.py‎

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
"""An admitted CPU copy of accepted native weights, never a training candidate."""
2+
3+
import copy
4+
import threading
5+
import time
6+
from contextlib import ExitStack
7+
from pathlib import Path
8+
9+
from rlm.v100.common import atomic_json
10+
from rlm.v100.competition import helper_client, managed_server
11+
from rlm.v100.protection import file_hash
12+
from rlm.v100.researchers import available_ram_gib
13+
from rlm.v100.serving import assert_served_expert
14+
15+
16+
def cpu_profile(accepted: dict) -> dict:
17+
if accepted["runtime"].get("backend", "llamacpp") not in ("llamacpp", "native"):
18+
raise ValueError("CPU chat requires accepted native GGUF weights")
19+
profile = copy.deepcopy(accepted)
20+
profile["server"].update(
21+
gpu_layers=0,
22+
slots=1,
23+
threads=2,
24+
context_per_slot=8192,
25+
batch_size=64,
26+
ubatch_size=32,
27+
flash_attention="off",
28+
cache_type="f16",
29+
draft_model="",
30+
draft_tokens=0,
31+
)
32+
profile["runtime"].update(
33+
base_url="http://127.0.0.1:8094",
34+
context_window=8192,
35+
max_output_tokens=1024,
36+
enable_thinking=False,
37+
max_timeout=600,
38+
)
39+
profile["memory"].update(chunk_tokens=512, fanout=4, retrieve_count=4)
40+
profile.setdefault("resources", {}).pop("mtp_validation", None)
41+
profile["resources"].update(
42+
device="cpu",
43+
min_available_ram_gib=16,
44+
cpu_guard=True,
45+
min_free_ram_gib=4,
46+
)
47+
return profile
48+
49+
50+
class AcceptedCPUChat:
51+
def __init__(self, root: Path, directory: Path, stop: threading.Event | None = None):
52+
self.root, self.directory = root, directory
53+
self.stack = ExitStack()
54+
self.identity = None
55+
self.client = None
56+
self.last_used = 0.0
57+
self.sha256 = None
58+
self.profile = None
59+
self.stop = stop or threading.Event()
60+
61+
def close(self) -> None:
62+
active = self.client is not None
63+
self.stack.close()
64+
self.stack = ExitStack()
65+
self.client, self.identity, self.sha256, self.profile = None, None, None, None
66+
if active:
67+
atomic_json(
68+
self.directory / "accepted-cpu-chat-status.json",
69+
{
70+
"available": False,
71+
"state": "closed; accepted weights retained",
72+
},
73+
)
74+
75+
def maintain(self) -> None:
76+
if self.client is not None and (
77+
time.monotonic() - self.last_used > 180 or available_ram_gib() < 4
78+
):
79+
self.close()
80+
81+
def get(self, accepted: dict):
82+
if self.stop.is_set():
83+
raise RuntimeError("Mission is stopping; CPU chat cannot start")
84+
model = Path(accepted["server"]["model"])
85+
stat = model.stat()
86+
identity = (str(model.resolve()), stat.st_size, stat.st_mtime_ns)
87+
if self.identity != identity:
88+
self.close()
89+
if self.client is not None:
90+
try:
91+
assert_served_expert(
92+
self.client, self.profile, self.root, {"files": {"model-gguf": self.sha256}}
93+
)
94+
props = self.client.request("/props")
95+
if Path(props["model_path"]).resolve() != model.resolve():
96+
raise ValueError("CPU chat endpoint no longer serves accepted weights")
97+
except Exception:
98+
self.close()
99+
raise
100+
self.last_used = time.monotonic()
101+
return self.client
102+
required = max(16, stat.st_size / 2**30 * 1.5 + 4)
103+
if available_ram_gib() < required:
104+
raise ValueError(f"CPU chat needs {required:.1f} GiB available RAM; no model loaded")
105+
profile = cpu_profile(accepted)
106+
profile["resources"]["min_available_ram_gib"] = required
107+
profile["resources"]["max_rss_gib"] = stat.st_size / 2**30 * 1.5 + 2
108+
path = self.directory / "accepted-cpu-chat.json"
109+
atomic_json(path, profile)
110+
self.sha256 = file_hash(model)
111+
try:
112+
actual = self.stack.enter_context(
113+
managed_server(
114+
path, self.root, self.directory / "accepted-cpu-chat.log", cancel=self.stop
115+
)
116+
)
117+
client = helper_client(actual, self.root)
118+
assert_served_expert(client, actual, self.root, {"files": {"model-gguf": self.sha256}})
119+
except Exception:
120+
self.close()
121+
raise
122+
self.client, self.identity = client, identity
123+
self.profile = actual
124+
self.last_used = time.monotonic()
125+
client.activity_actor = "chat-master-cpu"
126+
atomic_json(
127+
self.directory / "accepted-cpu-chat-status.json",
128+
{
129+
"available": True,
130+
"accepted_model_sha256": self.sha256,
131+
"model": str(model),
132+
"context": 8192,
133+
"threads": 2,
134+
"required_available_ram_gib": required,
135+
"scope": "Identical accepted weights, CPU only; not a promoted candidate",
136+
},
137+
)
138+
return client

‎rlm/v100/competition.py‎

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
import os
66
import subprocess
77
import sys
8+
import threading
89
import time
910
from concurrent.futures import ThreadPoolExecutor
1011
from contextlib import ExitStack, contextmanager
1112
from pathlib import Path
1213
from urllib.parse import urlparse
1314

15+
import psutil
1416
import requests
1517

1618
from rlm.clients.llamacpp import LlamaCppClient
@@ -48,8 +50,33 @@ def require_idle_gpu() -> None:
4850
)
4951

5052

53+
def guard_cpu_process(
54+
process, resources: dict, stop: threading.Event, cancel: threading.Event | None = None
55+
) -> None:
56+
"""Retire only this owned CPU process when resources or mission lifetime end."""
57+
while not stop.wait(1) and process.poll() is None:
58+
try:
59+
rss = psutil.Process(process.pid).memory_info().rss / 2**30
60+
except psutil.NoSuchProcess:
61+
return
62+
if (
63+
(cancel is not None and cancel.is_set())
64+
or available_ram_gib() < resources["min_free_ram_gib"]
65+
or rss > resources["max_rss_gib"]
66+
):
67+
process.terminate()
68+
try:
69+
process.wait(timeout=10)
70+
except subprocess.TimeoutExpired:
71+
process.kill()
72+
process.wait()
73+
return
74+
75+
5176
@contextmanager
52-
def managed_server(profile_path: Path, root: Path, log_path: Path):
77+
def managed_server(
78+
profile_path: Path, root: Path, log_path: Path, cancel: threading.Event | None = None
79+
):
5380
profile = load_profile(profile_path, root)
5481
from rlm.v100.remote_helper import remote_profile
5582
from rlm.v100.scratch_master import is_scratch, verify
@@ -106,13 +133,24 @@ def managed_server(profile_path: Path, root: Path, log_path: Path):
106133
stderr=subprocess.STDOUT,
107134
env=environment,
108135
)
136+
stop_guard = threading.Event()
137+
guard = None
138+
if cpu and profile.get("resources", {}).get("cpu_guard"):
139+
guard = threading.Thread(
140+
target=guard_cpu_process,
141+
args=(process, profile["resources"], stop_guard, cancel),
142+
daemon=True,
143+
)
144+
guard.start()
109145
try:
110146
if cpu:
111147
os.setpriority(os.PRIO_PROCESS, process.pid, 10)
112148
deadline = time.monotonic() + profile["runtime"]["max_timeout"]
113149
with requests.Session() as session:
114150
session.trust_env = False
115151
while True:
152+
if cancel is not None and cancel.is_set():
153+
raise RuntimeError("Managed server startup cancelled by mission shutdown")
116154
if process.poll() is not None:
117155
raise RuntimeError(f"Managed server exited; inspect {log_path}")
118156
if time.monotonic() >= deadline:
@@ -129,6 +167,9 @@ def managed_server(profile_path: Path, root: Path, log_path: Path):
129167
time.sleep(0.25)
130168
yield profile
131169
finally:
170+
stop_guard.set()
171+
if guard is not None:
172+
guard.join(timeout=12)
132173
if process.poll() is None:
133174
process.terminate()
134175
try:

0 commit comments

Comments
 (0)