File size: 3,619 Bytes
630cd8b 48eb0c0 630cd8b 48eb0c0 630cd8b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | """
Schedule-locked warmup for the Detect-Person chute.
TurboVision challenges fire at blocks that are multiples of `WINDOW_BLOCKS`
(300), ~61 min apart. A cold chute returns HTTP 503 instantly and the
validator's own warmup helper is broken, so a cold window scores 0. This
daemon keeps an instance alive only for the minutes around each window:
- wakes ~LEAD_SECONDS before the next challenge block
- pings /predict every PING_EVERY_S until TRAIL_SECONDS past the block
- sleeps the rest of the hour (instance scales to zero on its own)
Run it wherever it can reach api.chutes.ai and the bittensor chain. It only
needs CHUTES_API_KEY in the environment (source your .env).
"""
import base64
import os
import time
import urllib.request
import json
SLUG = os.getenv("SV_CHUTE_SLUG", "aiyoshida-turbovision-dev0524-shrimp")
WINDOW_BLOCKS = int(os.getenv("SV_WINDOW_BLOCKS", "300"))
# Measured cold-start delay across 16 real windows: min 179s, median 221s, max 508s.
# 420s lead missed the tail (one window came up at 506-508s, after the challenge
# block had already fired). Lead now covers the observed max with real margin.
LEAD_SECONDS = int(os.getenv("SV_LEAD_SECONDS", "720")) # start warming T-12min
TRAIL_SECONDS = int(os.getenv("SV_TRAIL_SECONDS", "360")) # keep warm to T+6min
PING_EVERY_S = int(os.getenv("SV_PING_EVERY_S", "30"))
BLOCK_TIME_S = float(os.getenv("SV_BLOCK_TIME_S", "12.3"))
API_KEY = os.environ["CHUTES_API_KEY"]
_TINY_PNG = base64.b64decode(
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+P+/HgAFhAJ/wlseKgAAAABJRU5ErkJggg=="
)
_PING_BODY = json.dumps(
{"frames": [{"frame_id": 0, "data": base64.b64encode(_TINY_PNG).decode()}],
"meta": {"batch_size": 1, "n_keypoints": 0}}
).encode()
def log(msg: str) -> None:
print(f"{time.strftime('%Y-%m-%d %H:%M:%S')} {msg}", flush=True)
def current_block() -> int:
from bittensor import Subtensor
st = Subtensor(network=os.getenv("BITTENSOR_SUBTENSOR_ENDPOINT", "finney"))
return int(st.get_current_block())
def ping() -> str:
req = urllib.request.Request(
f"https://{SLUG}.chutes.ai/predict",
data=_PING_BODY,
headers={"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"},
method="POST",
)
try:
with urllib.request.urlopen(req, timeout=120) as r:
ok = json.loads(r.read()).get("success")
return f"200 success={ok}"
except urllib.error.HTTPError as e:
return f"{e.code} {e.read()[:120].decode(errors='ignore')}"
except Exception as e:
return f"{type(e).__name__}: {e}"
def seconds_to_next_window() -> tuple[int, float]:
b = current_block()
next_block = ((b // WINDOW_BLOCKS) + 1) * WINDOW_BLOCKS
return next_block, (next_block - b) * BLOCK_TIME_S
def main() -> None:
log(f"warmup daemon start: slug={SLUG} lead={LEAD_SECONDS}s trail={TRAIL_SECONDS}s")
while True:
next_block, eta = seconds_to_next_window()
sleep_for = eta - LEAD_SECONDS
if sleep_for > 0:
log(f"next challenge block {next_block} in ~{eta/60:.1f} min; sleeping {sleep_for/60:.1f} min")
time.sleep(min(sleep_for, 1800))
continue
deadline = time.time() + (eta if eta > 0 else 0) + TRAIL_SECONDS
log(f"warming for block {next_block} until T+{TRAIL_SECONDS}s")
while time.time() < deadline:
log(f" ping -> {ping()}")
time.sleep(PING_EVERY_S)
log(f"window {next_block} done; releasing instance")
time.sleep(60)
if __name__ == "__main__":
main()
|