feat(runner): LIGHT_OPS dispatch prefers the home helper (m4pro)

bg_remove/ffmpeg_frames/ffprobe rank m4pro first (direct ~8ms link), then the
ultras, work-site machines last — so cheap jobs stop occupying the big GPUs
and qwen-image-edit / trellis / seedvr2 always find an ultra free. All other
operators keep the default local-first order.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
John King 2026-08-24 11:55:05 +10:00
parent b3de1a4e35
commit 03e8676db0

View File

@ -28,6 +28,18 @@ JOBS_DIR = db.DATA / "jobs"
# primary-only semaphore because the cloud API keys live only on the primary.
LANE_LIMITS = {"cpu": 3, "net": 6}
# Operators cheap enough (CPU-light, small assets) to keep OFF the big GPUs so
# qwen-image-edit / trellis / seedvr2 always find an ultra free. For these the
# dispatcher prefers the home helper on a direct link; every other operator keeps
# the default local-first order (i.e. still prefers the fast ultras).
LIGHT_OPS = {"bg_remove_local", "ffmpeg_frames", "ffprobe"}
# Lower rank = tried first for a LIGHT_OP. m4 (m4pro) is the home helper on a direct
# ~8ms link; the two ultras (local=m3ultra, m1=ultra) come next so a busy m4pro still
# falls back fast; the work-site machines are Sydney-DERP-relayed from the farm
# controller, so they are the last resort.
# the primary is named by hostname since 2026-08-05 (was "local"); keep both keys
_LIGHT_ORDER = {"m4": 0, "m1": 1, "m3ultra": 2, "local": 2, "studio": 3, "m4mini": 4, "mini": 5}
class Runner:
def __init__(self):
@ -210,9 +222,13 @@ class Runner:
async def _acquire_node(self, op_id: str, lane: str) -> dict:
"""Return the first node with free capacity in this lane that supports the
operator (local always does; a remote must list it + be reachable). Polls."""
operator (local always does; a remote must list it + be reachable). Polls.
LIGHT_OPS prefer the home helper (m4pro) so the ultras stay free for heavy work."""
order = self.gpu_nodes
if op_id in LIGHT_OPS:
order = sorted(order, key=lambda n: _LIGHT_ORDER.get(n.get("name", ""), 9))
while True:
for n in self.gpu_nodes:
for n in order:
if not remote.node_supports(n, op_id):
continue
cap = self._node_capacity(n, lane)