feather-a10g-large-runtime / overlay /scripts /autoresearch_may03_loop.py
icarus112's picture
Upload folder using huggingface_hub
22741d9 verified
#!/usr/bin/env python3
"""Continuous Feather autoresearch loop for local RTX 3060.
Protocol:
- One GPU owner, sequential runs only.
- 300s training budget, redirected logs.
- Parse val_bpb / metrics JSON from disk.
- Append TSV ledger.
- Keep searching until hard gate is reached or process is killed.
This loop mutates runtime env first because current Feather exposes most active
architecture/optimizer knobs through HYDRA_* gates. Code edits can be added as
candidate generators after the env frontier is exhausted.
"""
from __future__ import annotations
import itertools
import json
import os
import re
import shlex
import subprocess
import time
from pathlib import Path
ROOT = Path('/home/mikeb/work/feather')
LOGDIR = ROOT / 'logs' / 'autoresearch_may03'
LEDGER = ROOT / 'autoresearch_may03_results.tsv'
TARGET_BPB = float(os.environ.get('AUTORESEARCH_TARGET_BPB', '1.60'))
# Strict autoresearch cadence: train.py gets HYDRA_TIME_BUDGET=300; wrapper only
# allows startup + final eval overhead. Do not let one candidate occupy the GPU
# for 10-12 minutes unless it is genuinely hung.
RUN_TIMEOUT = int(os.environ.get('AUTORESEARCH_RUN_TIMEOUT', '430'))
LOGDIR.mkdir(parents=True, exist_ok=True)
if not LEDGER.exists():
LEDGER.write_text('ts\tcommit\tcandidate\tval_bpb\tpeak_tps\tmedian_tps\tmemory_gb\tstatus\tdescription\tlog\n')
BASE = {
'LD_LIBRARY_PATH': '/usr/lib/wsl/lib:/usr/local/cuda/lib64',
'PYTORCH_CUDA_ALLOC_CONF': 'expandable_segments:True',
'HF_TOKEN': '',
'HUGGINGFACE_HUB_TOKEN': '',
'WANDB_DISABLED': 'true',
'HYDRA_USE_NEMOTRON': '1',
'HYDRA_USE_FULL_BLEND': '1',
'HYDRA_SAMPLED_SOFTMAX': '1024',
'HYDRA_SOFTCAP_CLAMP': '1',
'HYDRA_SEQ_LEN': '1024',
'HYDRA_HEADDIM': '32',
'HYDRA_EXPAND': '3',
'HYDRA_BATCH_SIZE': '8',
'HYDRA_TOTAL_BATCH': '16384',
'HYDRA_D_MODEL': '160',
'HYDRA_N_LAYER': '20',
'HYDRA_D_STATE': '64',
'HYDRA_TIME_BUDGET': '300',
'HYDRA_ENGRAM_N_COLUMNS': '16384',
'HYDRA_ENGRAM_TOPK': '64',
'HYDRA_GDN_LAYERS': '',
'HYDRA_MTP_K': '1',
'HYDRA_USE_MDLM': '0',
'HYDRA_MUON_COMPILE': '0',
'HYDRA_MUON_NS_STEPS': '2', # promoted from TPS-11 receipt
'HYDRA_MATRIX_LR': '0.04',
'HYDRA_EMBED_LR': '0.6',
'HYDRA_UNEMBED_LR': '0.004',
'HYDRA_DT_BIAS_LR': '0.6',
'HYDRA_LOCAL_SHARDS_ONLY': '1',
'HYDRA_BACKGROUND_PREFETCH': '0',
'HYDRA_STREAM_SHUFFLE_BUFFER': '256',
'HYDRA_STREAM_PREFETCH': '16',
'HYDRA_TOKEN_PREFETCH': '4',
'HYDRA_TOKEN_CACHE_GB': '1',
'HYDRA_CKPT_INTERVAL': '2000',
'HYDRA_MID_VAL_INTERVAL': '0',
'HYDRA_HTM_SUBSAMPLE': '128',
'HYDRA_EVAL_BATCH': '1',
# HYDRA_EVAL_TOKENS removed (audit 2026-05-09, issue #15): the previous
# 1024-token eval reduced "20% factual" to a coin flip — every digit of
# quality signal we logged was within sampling noise. Defer to the
# prepare.EVAL_TOKENS default (~21M) or the 5M floor in eval_quality.py.
'HYDRA_CE_CHUNK': '32',
'HYDRA_SKIP_FACTUAL_EVAL': '1',
'HYDRA_RESUME_CKPT': 'none',
'UV_PYTHON': '/usr/bin/python3',
}
# Ordered from lowest-risk/promising to wider/radical. Infinite outer loop will
# revisit with perturbations after first pass.
CANDIDATES: list[tuple[str, dict[str, str], str]] = [
# Plateau-escape candidates: stronger than tiny LR nudges. These attack
# the 5-minute validation plateau by changing effective optimization,
# temporal capacity, and memory pressure while keeping full architecture.
# Real z-loss axis was tested after wiring fix: z=0.001 regressed
# (2.0446 vs best 2.0237). Return to default z=1e-4 and mutate the
# discovered l16/d192 basin more aggressively.
('basin_l16d192_lr085_emb11', {'HYDRA_TOTAL_BATCH':'32768','HYDRA_N_LAYER':'16','HYDRA_D_MODEL':'192','HYDRA_MATRIX_LR':'0.085','HYDRA_EMBED_LR':'1.1'}, 'basin: l16d192 hotter LR default z'),
('basin_l16d192_lr10_emb13', {'HYDRA_TOTAL_BATCH':'32768','HYDRA_N_LAYER':'16','HYDRA_D_MODEL':'192','HYDRA_MATRIX_LR':'0.10','HYDRA_EMBED_LR':'1.3'}, 'basin: l16d192 max hot LR default z'),
('basin_l16d192_lr065_emb09', {'HYDRA_TOTAL_BATCH':'32768','HYDRA_N_LAYER':'16','HYDRA_D_MODEL':'192','HYDRA_MATRIX_LR':'0.065','HYDRA_EMBED_LR':'0.9'}, 'basin: l16d192 moderate LR default z'),
('basin_l16d192_ns1p5_nope_ns2_fasttb', {'HYDRA_TOTAL_BATCH':'24576','HYDRA_N_LAYER':'16','HYDRA_D_MODEL':'192','HYDRA_MATRIX_LR':'0.075','HYDRA_EMBED_LR':'1.0'}, 'basin: l16d192 TB24576 more updates default z'),
('basin_l16d192_dstate48', {'HYDRA_TOTAL_BATCH':'32768','HYDRA_N_LAYER':'16','HYDRA_D_MODEL':'192','HYDRA_D_STATE':'48','HYDRA_MATRIX_LR':'0.075','HYDRA_EMBED_LR':'1.0'}, 'basin: l16d192 smaller d_state faster updates'),
('basin_l16d192_dstate80', {'HYDRA_TOTAL_BATCH':'32768','HYDRA_N_LAYER':'16','HYDRA_D_MODEL':'192','HYDRA_D_STATE':'80','HYDRA_MATRIX_LR':'0.075','HYDRA_EMBED_LR':'1.0'}, 'basin: l16d192 d_state80 capacity'),
('basin_l18d160_hot_defaultz', {'HYDRA_TOTAL_BATCH':'32768','HYDRA_N_LAYER':'18','HYDRA_D_MODEL':'160','HYDRA_MATRIX_LR':'0.075','HYDRA_EMBED_LR':'1.0'}, 'basin: valid deeper l18d160 default z'),
# High-leverage evolutionary front around the discovered winner l16/d192.
# This is no longer tiny-knob search: change shape + optimizer together.
('evo_l16d192_lr075_10', {'HYDRA_TOTAL_BATCH':'32768','HYDRA_Z_LOSS_WEIGHT':'0.001','HYDRA_N_LAYER':'16','HYDRA_D_MODEL':'192','HYDRA_MATRIX_LR':'0.075','HYDRA_EMBED_LR':'1.0'}, 'evo: l16d192 with hotter LR for 300s descent'),
('evo_l16d192_lr05_07', {'HYDRA_TOTAL_BATCH':'32768','HYDRA_Z_LOSS_WEIGHT':'0.001','HYDRA_N_LAYER':'16','HYDRA_D_MODEL':'192','HYDRA_MATRIX_LR':'0.05','HYDRA_EMBED_LR':'0.7'}, 'evo: l16d192 slightly cooler stability'),
('evo_l16d208', {'HYDRA_TOTAL_BATCH':'32768','HYDRA_Z_LOSS_WEIGHT':'0.001','HYDRA_N_LAYER':'16','HYDRA_D_MODEL':'208','HYDRA_MATRIX_LR':'0.06','HYDRA_EMBED_LR':'0.8'}, 'evo: l16 wider d208'),
('evo_l14d224', {'HYDRA_TOTAL_BATCH':'32768','HYDRA_Z_LOSS_WEIGHT':'0.001','HYDRA_N_LAYER':'14','HYDRA_D_MODEL':'224','HYDRA_MATRIX_LR':'0.06','HYDRA_EMBED_LR':'0.8'}, 'evo: l14 d224 speed/capacity trade'),
('evo_l12d256', {'HYDRA_TOTAL_BATCH':'32768','HYDRA_Z_LOSS_WEIGHT':'0.001','HYDRA_N_LAYER':'12','HYDRA_D_MODEL':'256','HYDRA_MATRIX_LR':'0.06','HYDRA_EMBED_LR':'0.8'}, 'evo: l12 d256 wide-frontier probe'),
('evo_l10d288', {'HYDRA_TOTAL_BATCH':'32768','HYDRA_Z_LOSS_WEIGHT':'0.001','HYDRA_N_LAYER':'10','HYDRA_D_MODEL':'288','HYDRA_MATRIX_LR':'0.06','HYDRA_EMBED_LR':'0.8'}, 'evo: l10 d288 radical width probe'),
('evo_l16d192_k768', {'HYDRA_TOTAL_BATCH':'32768','HYDRA_Z_LOSS_WEIGHT':'0.001','HYDRA_N_LAYER':'16','HYDRA_D_MODEL':'192','HYDRA_SAMPLED_SOFTMAX':'768','HYDRA_MATRIX_LR':'0.06','HYDRA_EMBED_LR':'0.8'}, 'evo: l16d192 lower sampled softmax for more updates'),
('evo_l16d192_k512', {'HYDRA_TOTAL_BATCH':'32768','HYDRA_Z_LOSS_WEIGHT':'0.001','HYDRA_N_LAYER':'16','HYDRA_D_MODEL':'192','HYDRA_SAMPLED_SOFTMAX':'512','HYDRA_MATRIX_LR':'0.06','HYDRA_EMBED_LR':'0.8'}, 'evo: l16d192 K512 throughput/calibration probe'),
('evo_l16d192_tb16384', {'HYDRA_TOTAL_BATCH':'16384','HYDRA_Z_LOSS_WEIGHT':'0.001','HYDRA_N_LAYER':'16','HYDRA_D_MODEL':'192','HYDRA_MATRIX_LR':'0.06','HYDRA_EMBED_LR':'0.8'}, 'evo: l16d192 smaller TB more optimizer steps'),
('escape_tb32768_z001_ns2_lr_hi', {'HYDRA_TOTAL_BATCH':'32768','HYDRA_Z_LOSS_WEIGHT':'0.001','HYDRA_MATRIX_LR':'0.06','HYDRA_EMBED_LR':'0.8'}, 'plateau escape: faster 300s descent with champion TB/zloss'),
('escape_tb32768_z001_ns2_lr_lo', {'HYDRA_TOTAL_BATCH':'32768','HYDRA_Z_LOSS_WEIGHT':'0.001','HYDRA_MATRIX_LR':'0.025','HYDRA_EMBED_LR':'0.45'}, 'plateau escape: lower LR calibration'),
('escape_tb32768_ns2_dstate96', {'HYDRA_TOTAL_BATCH':'32768','HYDRA_Z_LOSS_WEIGHT':'0.001','HYDRA_D_STATE':'96'}, 'plateau escape: extra SSM state capacity'),
('escape_tb32768_ns2_l18_d176', {'HYDRA_TOTAL_BATCH':'32768','HYDRA_Z_LOSS_WEIGHT':'0.001','HYDRA_N_LAYER':'18','HYDRA_D_MODEL':'176'}, 'plateau escape: trade depth for width at similar budget'),
('escape_tb32768_ns2_l16_d192', {'HYDRA_TOTAL_BATCH':'32768','HYDRA_Z_LOSS_WEIGHT':'0.001','HYDRA_N_LAYER':'16','HYDRA_D_MODEL':'192'}, 'plateau escape: stronger width trade'),
('escape_tb32768_ns2_gdn3', {'HYDRA_TOTAL_BATCH':'32768','HYDRA_Z_LOSS_WEIGHT':'0.001','HYDRA_GDN_LAYERS':'3,7,11'}, 'plateau escape: reintroduce known GDN quality axis'),
('escape_tb32768_ns2_gdn5', {'HYDRA_TOTAL_BATCH':'32768','HYDRA_Z_LOSS_WEIGHT':'0.001','HYDRA_GDN_LAYERS':'0,4,8,12,16'}, 'plateau escape: distributed 5-GDN quality axis'),
('escape_tb32768_ns2_enk128', {'HYDRA_TOTAL_BATCH':'32768','HYDRA_Z_LOSS_WEIGHT':'0.001','HYDRA_ENGRAM_TOPK':'128'}, 'plateau escape: wider engram read'),
('escape_tb32768_ns2_dr64', {'HYDRA_TOTAL_BATCH':'32768','HYDRA_Z_LOSS_WEIGHT':'0.001','HYDRA_SDR_DELTA_RANK':'64'}, 'plateau escape: wider SDR STE pipe despite prior weak amp'),
('escape_tb32768_ns3_lr_hi', {'HYDRA_MUON_NS_STEPS':'3','HYDRA_TOTAL_BATCH':'32768','HYDRA_Z_LOSS_WEIGHT':'0.001','HYDRA_MATRIX_LR':'0.06','HYDRA_EMBED_LR':'0.8'}, 'plateau escape: stable NS3 plus faster LR'),
('ns2_lr_m003', {'HYDRA_MATRIX_LR':'0.03'}, 'slightly lower matrix LR stabilizer'),
('ns2_lr_m005', {'HYDRA_MATRIX_LR':'0.05'}, 'slightly higher matrix LR for faster 300s descent'),
('ns2_embed04', {'HYDRA_EMBED_LR':'0.4'}, 'lower embed LR calibration'),
('ns2_embed08', {'HYDRA_EMBED_LR':'0.8'}, 'higher embed LR fast lexical fit'),
('ns2_dt03', {'HYDRA_DT_BIAS_LR':'0.3'}, 'lower dt-bias LR stability'),
('ns2_dt10', {'HYDRA_DT_BIAS_LR':'1.0'}, 'higher dt-bias adaptation'),
('ns2_dstate96', {'HYDRA_D_STATE':'96'}, 'more SSM state capacity'),
('ns2_dstate128', {'HYDRA_D_STATE':'128'}, 'max SSM state capacity probe'),
('ns2_enk128', {'HYDRA_ENGRAM_TOPK':'128'}, 'wider engram retrieval'),
('ns2_enk32', {'HYDRA_ENGRAM_TOPK':'32'}, 'narrower engram retrieval / less noise'),
('ns2_htm64', {'HYDRA_HTM_SUBSAMPLE':'64'}, 'more frequent HTM update'),
('ns2_htm256', {'HYDRA_HTM_SUBSAMPLE':'256'}, 'less HTM overhead/noise'),
('ns2_gdn_3_7_11', {'HYDRA_GDN_LAYERS':'3,7,11'}, 'retest 3-GDN trend on NS2'),
('ns2_gdn_0_4_8_12_16', {'HYDRA_GDN_LAYERS':'0,4,8,12,16'}, '5-GDN distributed depth'),
('ns2_gdn_0_1_2', {'HYDRA_GDN_LAYERS':'0,1,2'}, 'early GDN locality'),
('ns2_l18', {'HYDRA_N_LAYER':'18'}, 'shallower depth for more updates in budget'),
('ns2_l22', {'HYDRA_N_LAYER':'22'}, 'deeper temporal hierarchy if fits'),
('ns2_d176', {'HYDRA_D_MODEL':'176'}, 'slightly wider model'),
('ns2_d192', {'HYDRA_D_MODEL':'192'}, 'wider model capacity probe'),
('ns3_gdn_3_7_11', {'HYDRA_MUON_NS_STEPS':'3','HYDRA_GDN_LAYERS':'3,7,11'}, 'known GDN axis with stable Muon NS3'),
('ns3_tb32768_z001', {'HYDRA_MUON_NS_STEPS':'3','HYDRA_TOTAL_BATCH':'32768','HYDRA_Z_LOSS_WEIGHT':'0.001'}, 'champion-ish optimizer defaults'),
]
STEP_RE = re.compile(r'^step=\d+ .*?bpb=([0-9.]+).*?tps=([0-9.]+)', re.M)
VAL_RE = re.compile(r'val_bpb:\s*([0-9.]+)')
METRICS_RE = re.compile(r'\[METRICS_JSON\]\s*(\{.*\})')
def current_commit() -> str:
return subprocess.check_output(['git','rev-parse','--short','HEAD'], cwd=ROOT, text=True).strip()
def completed_names() -> set[str]:
done: set[str] = set()
if not LEDGER.exists():
return done
for line in LEDGER.read_text(errors='ignore').splitlines()[1:]:
parts = line.split('\t')
if len(parts) >= 3:
done.add(parts[2])
return done
def best_seen() -> float:
best = 999.0
# Parse the TSV ledger first. Its rows are not `val_bpb:` log lines.
if LEDGER.exists():
for line in LEDGER.read_text(errors='ignore').splitlines()[1:]:
parts = line.split('\t')
if len(parts) >= 4:
try:
v = float(parts[3])
except ValueError:
continue
if v > 0:
best = min(best, v)
# Also seed from known one-off receipts.
for path in [ROOT/'run_tps11_ns2.log', ROOT/'run_tps7_bs10.log', ROOT/'run_tps1_htm256.log']:
if not path.exists():
continue
txt = path.read_text(errors='ignore')
for m in VAL_RE.finditer(txt):
best = min(best, float(m.group(1)))
return best
def parse_log(path: Path):
txt = path.read_text(errors='ignore') if path.exists() else ''
vals = [float(m.group(1)) for m in VAL_RE.finditer(txt)]
pairs = [(float(a), float(b)) for a,b in STEP_RE.findall(txt)]
tps = [b for _, b in pairs if b > 0]
peak_tps = max(tps) if tps else 0.0
med_tps = sorted(tps)[len(tps)//2] if tps else 0.0
mem_gb = 0.0
metrics = None
mm = list(METRICS_RE.finditer(txt))
if mm:
try:
metrics = json.loads(mm[-1].group(1))
mem_gb = float(metrics.get('peak_vram_mb', 0.0)) / 1024.0
except Exception:
pass
if vals:
return vals[-1], peak_tps, med_tps, mem_gb, 'ok', metrics
if 'out of memory' in txt.lower() or 'OutOfMemory' in txt or 'CUDA driver error: out of memory' in txt:
return 0.0, peak_tps, med_tps, mem_gb, 'crash_oom', metrics
if 'Traceback' in txt or 'RuntimeError' in txt or 'AssertionError' in txt:
return 0.0, peak_tps, med_tps, mem_gb, 'crash', metrics
return 0.0, peak_tps, med_tps, mem_gb, 'no_val', metrics
def append(row: list[str]) -> None:
with LEDGER.open('a') as f:
f.write('\t'.join(row) + '\n')
def perturb_candidates(round_idx: int):
# Deterministic widening after first pass: combine the best-known NS2 with
# small LR/zloss/GDN/engram perturbations. Keeps generating work forever.
lrs = ['0.025','0.03','0.035','0.04','0.045','0.05']
embeds = ['0.45','0.55','0.6','0.7']
zloss = ['0.0001','0.0005','0.001','0.002']
gdns = ['', '3,7,11', '0,4,8,12,16', '0,1,2']
for i, (mlr, elr, zl, gdn) in enumerate(itertools.product(lrs, embeds, zloss, gdns)):
name = f'auto_r{round_idx:02d}_{i:03d}'
yield name, {
'HYDRA_MUON_NS_STEPS': '2',
'HYDRA_MATRIX_LR': mlr,
'HYDRA_EMBED_LR': elr,
'HYDRA_Z_LOSS_WEIGHT': zl,
'HYDRA_GDN_LAYERS': gdn,
}, f'auto grid ns2 mlr={mlr} embed={elr} z={zl} gdn={gdn or "none"}'
def run_candidate(name: str, delta: dict[str, str], desc: str, best: float):
ts = time.strftime('%Y%m%d_%H%M%S')
log = LOGDIR / f'{ts}_{name}.log'
env = os.environ.copy()
env.update(BASE)
env.update(delta)
cmd = ['taskset','-c','0-15', './.venv/bin/python', '-u', 'train.py']
print(f'[{time.strftime("%F %T")}] RUN {name} best={best:.6f} desc={desc}', flush=True)
with log.open('w') as f:
f.write(f'=== {name} ===\n')
f.write(f'desc={desc}\n')
f.write('env_delta=' + json.dumps(delta, sort_keys=True) + '\n')
f.flush()
try:
rc = subprocess.run(cmd, cwd=ROOT, env=env, stdout=f, stderr=subprocess.STDOUT, timeout=RUN_TIMEOUT).returncode
except subprocess.TimeoutExpired:
rc = 124
f.write('\n[TIMEOUT]\n')
val, peak, med, mem, status0, metrics = parse_log(log)
if status0 == 'ok':
status = 'keep' if val < best else 'discard'
else:
status = status0
append([
time.strftime('%F_%T'), current_commit(), name, f'{val:.6f}', f'{peak:.0f}', f'{med:.0f}', f'{mem:.2f}', status, desc.replace('\t',' '), str(log)
])
print(f'[{time.strftime("%F %T")}] DONE {name} val={val:.6f} peak={peak:.0f} med={med:.0f} mem={mem:.2f} status={status} log={log}', flush=True)
return val if status == 'keep' else best, status
def main():
best = best_seen()
one_shot = os.environ.get('AUTORESEARCH_ONE_SHOT', '0') == '1'
print(f'START autoresearch may03 best_seen={best:.6f} target={TARGET_BPB:.6f} one_shot={one_shot}', flush=True)
round_idx = 0
done = completed_names()
while True:
stream = CANDIDATES if round_idx == 0 else list(perturb_candidates(round_idx))
for name, delta, desc in stream:
if name in done:
print(f'[{time.strftime("%F %T")}] SKIP {name} already ledgered', flush=True)
continue
best, status = run_candidate(name, delta, desc, best)
done.add(name)
if best <= TARGET_BPB:
print(f'HARDGATE_REACHED best={best:.6f} target={TARGET_BPB:.6f}', flush=True)
return
# Let CUDA/WSL settle and reduce fragmentation.
subprocess.run(['bash','-lc','python3 - <<"PY"\nimport torch\ntorch.cuda.empty_cache() if torch.cuda.is_available() else None\nPY'], cwd=ROOT, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
if one_shot:
print(f'ONE_SHOT_DONE best={best:.6f}', flush=True)
return
time.sleep(10)
round_idx += 1
if one_shot:
# No remaining unledgered candidates in the fixed queue; allow the
# perturbation generator on the next cron tick instead of looping in
# a long-lived process.
print(f'ONE_SHOT_NO_FIXED_CANDIDATE best={best:.6f}', flush=True)
return
if __name__ == '__main__':
main()