Commit Graph
3 Commits
Author SHA1 Message Date
Palash Debnath 5a615d2c66 feat(workers): package headless GPU nodes (#1638) (#1648)
Closes #1638.\n\nPackages headless GPU workers with durable enrollment, bounded artifact handling, cross-platform lifecycle cleanup, and regression coverage. Incorporates CodeRabbit, Greptile, CodeQL, and platform-CI findings before merge.
2026-08-24 16:32:56 +05:30
debpalash 037a5689de fix(worker): address legacy transport review findings 2026-08-11 23:49:46 +00:00
velixio b54cd28403 feat(workers): one gateway for GPU calls, and results too big for the wire
Two phases of the remote-GPU plan, landing together because neither is
useful alone: on a 4090 any render long enough to exercise the progress
lease also outgrows the 8 MiB message cap, so a gateway that routes work
remotely without an artifact transport just moves where the failure
happens.

**The gateway** (`services/gpu_gateway.py`) is the single owner of GPU
calling, model status, downloads and model load, for both targets —
`prewarm`, `run`, `status`, `download`. prewarm and run stay separate
because collapsing them loses the two-phase load/generate budget split
(#1033/#1037) that the worker protocol already mirrors. Admission moves
in here too: the old `check_gpu_admission` call read *local* pool stats,
so under Remote it would 429 on local saturation while the remote GPU
sat idle.

**Artifacts** now move out of band above a negotiated threshold. Bytes
land in an attempt-scoped `.part` file, are verified against a declared
sha256, and are renamed into place only on an explicit last chunk — a
transfer that arrives short, reordered, or simply stops commits nothing.
A resume rehashes what is already on disk, or the digest would attest
only to the tail, which is the exact case a resume exists to protect.

Two failure modes found while verifying this, both fixed with
mutation-checked regressions:

  * an oversized payload with no session (mid-reconnect, or a control
    plane too old to serve UploadResult) has nowhere to go. It must not
    enter `_pending` — an over-cap frame is re-sent on every reconnect,
    killing the session each time and stranding every other task — but
    it must stay retryable, unlike the size gate's TERMINAL verdict:
    nothing about the render is wrong, only the route to it.

  * the upload resume loop was bounded by "did the offset change", which
    a receiver alternating between two byte counts satisfies forever.
    The worker is single-slot by default, so that is not one lost upload
    but the machine, doing nothing else, until someone restarts it.
    Bounded by a round count instead.

The control stream is split into control and bulk queues so the
heartbeat this whole liveness model rests on cannot queue behind a
payload — `result_json` has no size cliff to catch it, and the next bulk
message added to the protocol would have reintroduced the stall
silently.

Live streaming stays on the control plane and now says so once per
socket: that route exists to put audio in the user's ear before the
sentence finishes, and paying queue admission plus a round trip per
utterance would spend the one thing it is for. Silence would have been
worse than the limit — the header badge would read "gpu2" while this
machine did all the work.

Backend 5236 passed, frontend 1807 passed. End-to-end verification on
real hardware has NOT been re-run since these changes; the CHANGELOG
claim for the Synthesize button waits on that.
2026-08-11 13:30:26 +05:30