OpenTransformer's picture
download
raw
18.2 kB
#!/usr/bin/env python3
"""Adaptive AGILLM4.x DiffusionBlock lease planner.
Prints five fields for shell callers:
<batch> <block_tokens> <max_layers> <block_id> <layer_offset> <steps>
agillm41_lease_decide.py still owns *how much* work a device can hold. This
planner adds *where* that work should land by combining live DBlock EMA/counts
with recent async side-update coverage, then reserving layers within a round so
small workers do not all chase the same slice.
"""
from __future__ import annotations
import argparse
import hashlib
import json
import os
import re
import subprocess
import sys
import time
from pathlib import Path
from typing import Any
try:
import fcntl # type: ignore
except Exception: # pragma: no cover - Linux path on Vast has this.
fcntl = None
TOTAL_LAYERS = int(os.environ.get("AGILLM41_TOTAL_LAYERS", "28"))
LAYERS_PER_BLOCK = int(os.environ.get("AGILLM41_LAYERS_PER_BLOCK", "7"))
NUM_BLOCKS = max(1, TOTAL_LAYERS // LAYERS_PER_BLOCK)
MASTER_LOG = Path(os.environ.get("AGILLM41_MASTER_LOG", "/workspace/agillm41_master_train.log"))
LEASE_STATE = Path(os.environ.get("AGILLM41_LEASE_STATE", "/workspace/agillm41_lease_state.json"))
PLAN_STATE = Path(os.environ.get("AGILLM41_LEASE_PLAN_STATE", "/workspace/agillm41_lease_plan_state.json"))
DECIDE = Path(os.environ.get("AGILLM41_LEASE_DECIDE", "/workspace/agillm41_lease_decide.py"))
TAIL_BYTES = int(os.environ.get("AGILLM41_PLAN_TAIL_BYTES", str(4 * 1024 * 1024)))
DBLOCK_RE = re.compile(
r"\[dblock\]\s+step=(?P<step>\d+)\s+block=(?P<block>\d+)\s+.*?"
r"counts=\[(?P<counts>[^\]]+)\]\s+ema=\[(?P<ema>[^\]]+)\]"
)
def read_json(path: Path, default: Any) -> Any:
try:
return json.loads(path.read_text())
except Exception:
return default
def write_json_atomic(path: Path, data: Any) -> None:
tmp = path.with_suffix(path.suffix + ".tmp")
tmp.write_text(json.dumps(data, indent=2, sort_keys=True))
tmp.replace(path)
def read_tail(path: Path, limit: int = TAIL_BYTES) -> str:
try:
with path.open("rb") as fh:
fh.seek(0, os.SEEK_END)
size = fh.tell()
fh.seek(max(0, size - limit), os.SEEK_SET)
return fh.read().decode("utf-8", "ignore")
except Exception:
return ""
def parse_nums(text: str, as_float: bool = False) -> list[float] | list[int]:
out: list[float] = []
for part in text.split(","):
part = part.strip()
if not part:
continue
try:
out.append(float(part) if as_float else int(float(part)))
except Exception:
pass
return out
def latest_dblock_stats(text: str) -> dict[str, Any]:
mode_idx = text.rfind("[dblock] DiffusionBlocks mode")
segment = text[mode_idx:] if mode_idx >= 0 else text
matches = list(DBLOCK_RE.finditer(segment)) or list(DBLOCK_RE.finditer(text))
if not matches:
return {
"step": 0,
"counts": [0 for _ in range(NUM_BLOCKS)],
"ema": [1.0 for _ in range(NUM_BLOCKS)],
"source": "default",
}
last = matches[-1]
counts = list(parse_nums(last.group("counts")))[:NUM_BLOCKS]
ema = list(parse_nums(last.group("ema"), as_float=True))[:NUM_BLOCKS]
while len(counts) < NUM_BLOCKS:
counts.append(0)
while len(ema) < NUM_BLOCKS:
ema.append(sum(ema) / len(ema) if ema else 1.0)
return {"step": int(last.group("step")), "counts": counts, "ema": ema, "source": "log"}
def async_coverage(text: str) -> dict[str, Any]:
layer_counts = [0 for _ in range(TOTAL_LAYERS)]
layer_last_step = [0 for _ in range(TOTAL_LAYERS)]
block_event_counts = [0 for _ in range(NUM_BLOCKS)]
events: list[dict[str, Any]] = []
current_step = 0
for line in text.splitlines():
if "async_side_update_applied" not in line or "{" not in line:
continue
try:
data = json.loads(line[line.index("{") :])
except Exception:
continue
try:
step = int(data.get("step") or 0)
except Exception:
step = 0
current_step = max(current_step, step)
try:
block_id = int(data.get("block_id") or 0)
except Exception:
block_id = 0
if 0 <= block_id < NUM_BLOCKS:
block_event_counts[block_id] += 1
layers = data.get("layers") or []
clean_layers = []
for layer in layers:
try:
layer_i = int(layer)
except Exception:
continue
if 0 <= layer_i < TOTAL_LAYERS:
layer_counts[layer_i] += 1
layer_last_step[layer_i] = max(layer_last_step[layer_i], step)
clean_layers.append(layer_i)
events.append(
{
"step": step,
"worker_id": data.get("worker_id"),
"block_id": block_id,
"layers": clean_layers,
"tok_per_sec": data.get("tok_per_sec"),
}
)
return {
"layer_counts": layer_counts,
"layer_last_step": layer_last_step,
"block_event_counts": block_event_counts,
"current_step": current_step,
"events": events[-80:],
}
def norm_high(values: list[float] | list[int]) -> list[float]:
vals = [float(v) for v in values]
lo = min(vals) if vals else 0.0
hi = max(vals) if vals else 0.0
if hi <= lo:
return [0.5 for _ in vals]
return [(v - lo) / (hi - lo) for v in vals]
def norm_low(values: list[float] | list[int]) -> list[float]:
vals = [float(v) for v in values]
lo = min(vals) if vals else 0.0
hi = max(vals) if vals else 0.0
if hi <= lo:
return [0.5 for _ in vals]
return [(hi - v) / (hi - lo) for v in vals]
def layer_window(block_id: int, offset: int, max_layers: int) -> list[int]:
start = block_id * LAYERS_PER_BLOCK
width = LAYERS_PER_BLOCK if max_layers >= LAYERS_PER_BLOCK else max(1, max_layers)
return [start + ((offset + i) % LAYERS_PER_BLOCK) for i in range(width)]
def stable_jitter(*parts: Any) -> float:
raw = "|".join(str(p) for p in parts).encode("utf-8", "ignore")
digest = hashlib.blake2b(raw, digest_size=4).hexdigest()
return int(digest, 16) / 0xFFFFFFFF * 0.01
def decide_capacity(worker: str) -> tuple[int, int, int]:
try:
cp = subprocess.run(
["python3", str(DECIDE), worker],
capture_output=True,
text=True,
timeout=45,
check=False,
)
fields = cp.stdout.strip().split()
if len(fields) >= 3:
return max(1, int(fields[0])), max(1, int(fields[1])), max(1, int(fields[2]))
except Exception:
pass
return 1, 128, 1
def disabled_reason(worker: str) -> str | None:
state = read_json(LEASE_STATE, {})
rec = state.get(worker, {}) if isinstance(state, dict) else {}
if not isinstance(rec, dict):
return None
text = " ".join(str(rec.get(k) or "") for k in ("failure", "failure_seen"))
low = text.lower()
if "disabled" in low:
return text.strip()[:200] or "disabled"
if worker.endswith("igpu") and ("directml" in low or "backward" in low or "runtimeerror" in low):
return text.strip()[:200] or "igpu backward failure"
return None
def build_scores(text: str) -> dict[str, Any]:
dblock = latest_dblock_stats(text)
coverage = async_coverage(text)
counts = [int(x) for x in dblock["counts"]]
ema = [float(x) for x in dblock["ema"]]
block_layer_merges = [
sum(coverage["layer_counts"][b * LAYERS_PER_BLOCK : (b + 1) * LAYERS_PER_BLOCK])
for b in range(NUM_BLOCKS)
]
block_last = [
max(coverage["layer_last_step"][b * LAYERS_PER_BLOCK : (b + 1) * LAYERS_PER_BLOCK] or [0])
for b in range(NUM_BLOCKS)
]
current_step = max(int(coverage["current_step"] or 0), int(dblock["step"] or 0))
block_stale = [max(0, current_step - step) if step else current_step for step in block_last]
ema_need = norm_high(ema)
count_need = norm_low(counts)
side_need = norm_low(block_layer_merges)
stale_need = norm_high(block_stale)
block_scores = []
for i in range(NUM_BLOCKS):
block_scores.append(
0.45 * ema_need[i]
+ 0.25 * count_need[i]
+ 0.20 * side_need[i]
+ 0.10 * stale_need[i]
)
layer_count_need = norm_low(coverage["layer_counts"])
layer_staleness = [max(0, current_step - x) if x else current_step for x in coverage["layer_last_step"]]
layer_stale_need = norm_high(layer_staleness)
layer_scores = []
for layer in range(TOTAL_LAYERS):
block = min(NUM_BLOCKS - 1, layer // LAYERS_PER_BLOCK)
never = 1.0 if coverage["layer_counts"][layer] == 0 else 0.0
layer_scores.append(
0.40 * layer_count_need[layer]
+ 0.30 * layer_stale_need[layer]
+ 0.15 * never
+ 0.15 * block_scores[block]
)
return {
"dblock": dblock,
"coverage": coverage,
"block_layer_merges": block_layer_merges,
"block_last": block_last,
"block_stale": block_stale,
"block_scores": block_scores,
"layer_scores": layer_scores,
"current_step": current_step,
}
def choose_assignment(worker: str, max_layers: int, scores: dict[str, Any], reservations: list[dict[str, Any]], round_id: str, history: list[dict[str, Any]] = None) -> dict[str, Any]:
reserved_layers = {
int(layer)
for item in reservations
for layer in item.get("layers", [])
if isinstance(layer, int) or str(layer).isdigit()
}
# Find worker's last assignment in history to implement sticky planning
last_block_id = None
last_layer_offset = None
if history:
for item in reversed(history):
if item.get("worker") == worker:
last_block_id = item.get("block_id")
last_layer_offset = item.get("layer_offset")
break
block_scores = scores["block_scores"]
layer_scores = scores["layer_scores"]
best: dict[str, Any] | None = None
for block_id in range(NUM_BLOCKS):
offsets = [0] if max_layers >= LAYERS_PER_BLOCK else list(range(LAYERS_PER_BLOCK))
for offset in offsets:
layers = layer_window(block_id, offset, max_layers)
collisions = sum(1 for layer in layers if layer in reserved_layers)
window_score = sum(layer_scores[layer] for layer in layers) / max(1, len(layers))
# Apply sticky boost: +0.8 if same block_id, +0.2 if also same layer_offset
boost = 0.0
if last_block_id is not None and block_id == last_block_id:
boost += 0.8
if last_layer_offset is not None and offset == last_layer_offset:
boost += 0.2
score = 1.35 * block_scores[block_id] + window_score - 2.75 * collisions + boost
score += stable_jitter(round_id, worker, block_id, offset)
candidate = {
"worker": worker,
"block_id": block_id,
"layer_offset": offset,
"layers": layers,
"score": score,
"collisions": collisions,
"block_score": block_scores[block_id],
"window_score": window_score,
}
if best is None or candidate["score"] > best["score"]:
best = candidate
assert best is not None
return best
def explain_reason(choice: dict[str, Any], scores: dict[str, Any]) -> dict[str, Any]:
dblock = scores["dblock"]
cov = scores["coverage"]
block_id = choice["block_id"]
return {
"block_id": block_id,
"layers": choice["layers"],
"score": round(float(choice["score"]), 4),
"collisions": choice["collisions"],
"dblock_ema": dblock["ema"],
"dblock_counts": dblock["counts"],
"block_layer_merges": scores["block_layer_merges"],
"selected_layer_merge_counts": [cov["layer_counts"][l] for l in choice["layers"]],
"selected_layer_last_step": [cov["layer_last_step"][l] for l in choice["layers"]],
"current_step": scores["current_step"],
}
def update_dynamic_blocks() -> None:
global LAYERS_PER_BLOCK, NUM_BLOCKS
state_path = Path(LEASE_STATE)
if not state_path.exists():
return
try:
state_data = json.loads(state_path.read_text())
except Exception:
return
active_workers = []
now = time.time()
for name, w in state_data.items():
ts = w.get("ts", 0)
if now - ts < 1800 and not disabled_reason(name):
active_workers.append(w)
if not active_workers:
return
has_ultra_low = any(w.get("max_layers", 1) <= 1 for w in active_workers)
has_low_cap = any(w.get("max_layers", 1) <= 2 for w in active_workers)
all_high_cap = all(w.get("max_layers", 1) >= 14 for w in active_workers)
all_mid_high = all(w.get("max_layers", 1) >= 7 for w in active_workers)
if all_high_cap:
chosen_lpb = 28
elif all_mid_high:
chosen_lpb = 14
elif has_ultra_low:
chosen_lpb = 2
elif has_low_cap:
chosen_lpb = 4
else:
chosen_lpb = 7
if TOTAL_LAYERS % chosen_lpb != 0:
chosen_lpb = 7
LAYERS_PER_BLOCK = chosen_lpb
NUM_BLOCKS = max(1, TOTAL_LAYERS // LAYERS_PER_BLOCK)
hot_config_path = Path("/workspace/hot_config.json")
try:
cfg = {}
if hot_config_path.exists():
cfg = json.loads(hot_config_path.read_text())
cfg["dblock_blocks"] = NUM_BLOCKS
tmp = hot_config_path.with_suffix(hot_config_path.suffix + ".tmp")
tmp.write_text(json.dumps(cfg, indent=2, sort_keys=True))
tmp.replace(hot_config_path)
except Exception as e:
print(f"[dblock_autotuning] Error writing hot_config: {e}", file=sys.stderr)
print(f"[dblock_autotuning] Dynamically selected LAYERS_PER_BLOCK = {LAYERS_PER_BLOCK}, NUM_BLOCKS = {NUM_BLOCKS} based on {len(active_workers)} active workers.", file=sys.stderr)
def main(argv: list[str]) -> int:
update_dynamic_blocks()
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument("worker", help="worker id, e.g. geth, laptop-cuda, vast-v100")
ap.add_argument("--round", default=os.environ.get("AGILLM41_LEASE_ROUND", "manual"))
ap.add_argument("--explain", action="store_true", help="explain choice to stderr")
ap.add_argument("--no-reserve", action="store_true", help="choose without updating round reservation state")
ap.add_argument("--allow-disabled", action="store_true")
args = ap.parse_args(argv)
reason = None if args.allow_disabled else disabled_reason(args.worker)
if reason:
if args.explain:
print(json.dumps({"event": "lease_plan_skipped", "worker": args.worker, "reason": reason}), file=sys.stderr)
return 2
batch, block_tokens, max_layers = decide_capacity(args.worker)
max_layers = max(1, min(LAYERS_PER_BLOCK, int(max_layers)))
text = read_tail(MASTER_LOG)
scores = build_scores(text)
PLAN_STATE.parent.mkdir(parents=True, exist_ok=True)
lock_path = PLAN_STATE.with_suffix(PLAN_STATE.suffix + ".lock")
with lock_path.open("a+") as lock_fh:
if fcntl is not None:
fcntl.flock(lock_fh, fcntl.LOCK_EX)
state = read_json(PLAN_STATE, {})
if not isinstance(state, dict) or state.get("round") != args.round:
state = {"round": args.round, "reservations": [], "history": []}
reservations = state.setdefault("reservations", [])
history = state.get("history", [])
choice = choose_assignment(args.worker, max_layers, scores, reservations, args.round, history)
choice.update({"batch": batch, "block_tokens": block_tokens, "max_layers": max_layers, "at": time.time()})
reason_json = explain_reason(choice, scores)
choice["reason"] = reason_json
if not args.no_reserve:
reservations.append(choice)
state["reservations"] = reservations[-64:]
hist = state.setdefault("history", [])
hist.append(choice)
state["history"] = hist[-256:]
state["last_scores"] = {
"block_scores": [round(float(x), 4) for x in scores["block_scores"]],
"dblock_ema": scores["dblock"]["ema"],
"dblock_counts": scores["dblock"]["counts"],
"block_layer_merges": scores["block_layer_merges"],
"current_step": scores["current_step"],
}
write_json_atomic(PLAN_STATE, state)
if fcntl is not None:
fcntl.flock(lock_fh, fcntl.LOCK_UN)
if args.explain:
print(json.dumps({"event": "lease_planned", "worker": args.worker, **reason_json}), file=sys.stderr)
state_data = read_json(LEASE_STATE, {})
rec = state_data.get(args.worker, {}) if isinstance(state_data, dict) else {}
tokps = rec.get("tokps") or rec.get("decision_tokps")
# Worker slow-start prevention: default to reasonable initial throughput values
if not tokps or tokps <= 0:
if "geth" in args.worker:
tokps = 20.0
elif "communist" in args.worker:
tokps = 20.0
elif "prime" in args.worker:
tokps = 8.0
elif "mcp" in args.worker:
tokps = 8.0
elif "laptop" in args.worker:
tokps = 5.0
else:
tokps = 15.0
target_duration = float(os.environ.get("AGILLM41_LEASE_TARGET_DURATION", "240"))
step_tokens = batch * block_tokens
# Enforce minimum steps of 5 to amortize startup/model loading overhead
steps = max(5, min(100, int(round((tokps * target_duration) / step_tokens))))
print(f"{batch} {block_tokens} {max_layers} {choice['block_id']} {choice['layer_offset']} {steps}")
return 0
if __name__ == "__main__":
raise SystemExit(main(sys.argv[1:]))

Xet Storage Details

Size:
18.2 kB
·
Xet hash:
88a3f6991d94dcbce5efafdd81249277bc3fb94846a214aa168b0d505abbf8c4

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.