LittlePlyResearch notebook

scheduler.py

python · 59 lines · Exact bytes

Line 1 ↗
Version & provenance · service-gpu-c205ec8f7f77

Recorded SHA-256 91051249b153ffbacac9b8cf48044203b1aad83ac7a362bf1e1ba59736e3832cRendered SHA-256 91051249b153ffbacac9b8cf48044203b1aad83ac7a362bf1e1ba59736e3832c

Apache-2.0 for original LittlePly code, declared by the project owner on 2026-10-28. Historical source bytes and hashes are unchanged. Third-party components retain their own licenses; Lichess data remain CC0-1.0. License notes. Line links are stable within this snapshot. Exact allowlisted source from immutable live GPU release 6133648b2926-c205ec8f7f77, verified against release manifest SHA-256 a1cdc3f5f55c96b3164bc6fd78cb37308d547b2d7f07865dd37ad1448f2f3a1e. Runtime owner confirmed GPU serving, event-stream/model warmup, BOT identity and zero restarts on 2026-10-28. Capacity is up to four casual standard games, with bullet games alone; UltraBullet, correspondence and variants are unsupported. Checkpoint remains 6133648b2926b38542ee3ee1251a1ba111d61da9a8ea69d1c5f0559b09933643. app_multi.py is the multi-game controller; app.py is its shared base, whose legacy standalone admission differs. This is a source subset; host configuration, launcher, state, secrets and private games are excluded. This is a serving change, not a new model or evaluation.

1"""Bounded per-game waiting set with earliest-deadline-first dispatch."""
2from dataclasses import dataclass
3import time
4
5def clock_window(state,color,age=0):
6    side='w' if color else 'b';remaining=max(0,state[side+'time']/1000-age)
7    increment=state.get(side+'inc',0)/1000
8    reserve=min(2.,max(.3,remaining*.1))
9    # Fast clocks get a usable subsecond budget; longer clocks retain room for
10    # a serialized burst. Queue time is included in the absolute deadline.
11    target=max(.6,remaining/10+increment*.6)
12    return max(.01,min(30.,target,remaining-reserve))
13
14def realtime_reason(tc):
15    if tc.get('type')!='clock':return 'timeControl'
16    limit,inc=tc.get('limit'),tc.get('increment')
17    if type(limit) is not int or type(inc) is not int or limit<0 or inc<0:return 'timeControl'
18    if limit+40*inc<30:return 'tooFast' # Lichess Bot API forbids UltraBullet.
19    return None
20
21def bullet_clock(limit,increment):
22    return limit+40*increment<180
23
24def expected_policy_seconds(tokens):
25    # Conservative envelope over short and near-context CPU load probes.
26    return .6+5.8*(min(tokens,1024)/1024)**2
27
28@dataclass
29class Request:
30    key:tuple
31    board:object
32    arrived:float
33    deadline:float
34    sequence:int
35    expired:bool=False
36
37class Scheduler:
38    def __init__(self,capacity):self.capacity=capacity;self.waiting={};self.sequence=0;self.running=None
39    def add(self,key,board,window,now=None):
40        now=time.monotonic() if now is None else now;gid=key[0]
41        if gid in self.waiting and self.waiting[gid].key==key:return
42        if gid not in self.waiting and len(self.waiting)>=self.capacity:raise OverflowError('per-game queue full')
43        self.sequence+=1;self.waiting[gid]=Request(key,board,now,now+window,self.sequence)
44    def remove(self,gid):self.waiting.pop(gid,None)
45    def next(self,valid,now=None):
46        if self.running:return None
47        for gid,request in list(self.waiting.items()):
48            if not valid(request.key):self.waiting.pop(gid)
49        if not self.waiting:return None
50        request=min(self.waiting.values(),key=lambda r:(r.deadline,r.sequence))
51        self.waiting.pop(request.key[0]);self.running=request;return request
52    def complete(self,key):
53        if self.running and self.running.key==key:
54            request=self.running;self.running=None;return request
55        return None
56
57def capacity_fits(windows,service_bound):
58    return bool(windows) and len(windows)*service_bound<=min(windows)
59