Skip to content

[REA-5060] Add distributed worker protocol and frame transport - #134

Open
Orion-Zheng wants to merge 7 commits into
mainfrom
andy/rea-5060-multi-gpu-worker
Open

Orion-Zheng wants to merge 7 commits into
mainfrom
andy/rea-5060-multi-gpu-worker

Conversation

@Orion-Zheng

@Orion-Zheng Orion-Zheng commented Aug 11, 2026

Copy link
Copy Markdown

Why

Multi-GPU models need per-rank lifecycle hooks and bounded frame transport without committing the package root to a stable distributed API. This is the foundation of the REA-5060 stack and can land independently of an engine host.

What Changed

Adds the experimental single-node DistributedWorker protocol, worker error types, and shared uint8 video frame transport. Torch remains lazy and optional at import time, and package tests keep the surface off the root exports. Frame-buffer and package tests accompany this layer; worker-group orchestration, the managed model adapter, and the runnable example are separate dependent PRs. Local Python 3.12 unit/contract tests, lint, and type checking pass. No NVIDIA/NCCL execution is claimed.

Orion-Zheng and others added 3 commits August 11, 2026 08:33
The standalone runtime has no multi-GPU story: a model that needs several GPUs
has nowhere to put the per-device half of itself, so the abstraction that solved
this in the previous runtime is brought across. This commit is only the move.

The six files land byte-identical, so this commit is the baseline every later
commit in the stack is reviewed against — it proves nothing was lost in
transit, and it separates "the code arrived" from "the code was adapted". The
adaptations this repo requires (its docstring style, its typing strictness, no
references to systems an outside reader cannot see) all follow as their own
commits on top, where they can be read as deliberate changes rather than hidden
inside a 750-line paste.

Nothing imports the package yet and nothing re-exports it, so this commit does
not change behaviour.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

Signed-off-by: Andy <andy@reactor.inc>
The move landed the code unchanged; this makes it belong here. Four kinds of
change, none of them behavioural — the ten protocol tests that came with the
package pass untouched, which is what makes that claim checkable.

Style and provenance. The copyright headers go, since this repo carries none.
References to systems an outside reader cannot see go too, with their content
kept where it was load-bearing: "a standing watchdog is not implemented" says
everything a reader needs without naming a tracker, and the dangling pointer to
an example that was never published is simply dropped. Docstrings gain the
one-line imperative summary the linter wants; the Sphinx roles stay, because
this repo already mixes them with Google sections.

Logging. This is the one seam that would have failed at runtime. get_logger has
the same name in both runtimes but returns a different object here: a structured
logger whose level methods take fields as keywords and reject positional args.
Two calls passed them, and both sit on failure paths — the worst place to learn
that a logging call raises. Every call now uses a static message plus fields, and
a spawned worker installs the runtime's own formatter instead of a bare one, so
its lines share the runtime's shape and carry the rank as a field rather than a
string prefix.

Surface. The five public names are re-exported from the top-level package,
because the supported surface here is what that package exports; a name reachable
only through a submodule reads as unsupported. What makes this safe is that torch
is imported lazily, inside the functions that use it, so importing the runtime
still pulls in nothing heavier than numpy. That rests on discipline rather than on
structure, so a test now pins it: a subprocess imports the package and asserts
torch never entered sys.modules.

Typing. Only two things needed saying. torch is an optional dependency the
checker cannot see, so its lazy imports carry a narrow suppression with the
reason attached. And a concrete setup() override necessarily narrows the base's
**setup_kwargs, which reads as an incompatible override — a property of the
abstraction that every model's worker will meet, so the base class now documents
it and the test suppresses it the same way a model would.

Signed-off-by: Andy <andy@reactor.inc>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Andy <andy@reactor.inc>
The README is the front page, and it described a runtime where a model runs on
one device. Multi-GPU changes what an author can build with it, so it belongs
here rather than only in the API docs.

One line in Highlights, and a paragraph in How it works placed after the
single-GPU example — because the point worth making is that the example does not
change. The model keeps its event loop, its session, and its output stream, and
gains a handle it drives from the same run() loop. Framed that way rather than as
a feature list, since the reason to reach for this is a model that outgrew one
device, not a wish to write distributed code.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Andy <andy@reactor.inc>
@Orion-Zheng Orion-Zheng changed the title [REA-5060][1/2] Give a model a way to run on more than one GPU [REA-5060] Give a model a way to run on more than one GPU Aug 11, 2026
@Orion-Zheng
Orion-Zheng marked this pull request as ready for review August 14, 2026 16:55
@Orion-Zheng
Orion-Zheng requested a review from a team as a code owner August 14, 2026 16:55
Copilot AI lite review requested due to automatic review settings August 14, 2026 16:55

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@Bryceoz
Bryceoz requested a review from tempusfrangit August 15, 2026 05:29
@tempusfrangit

Copy link
Copy Markdown
Contributor

The engine is a good approach. Failure taxonomy, the max-end-row trick, the shared-memory path, liveness, the teardown ladder, the ten protocol tests are solid.

The changes I'd like to see are the author surface. We need to be careful as once the names are importable they are minimally an implicit contract and likely to be taken as an explicit contract.

Today multi-GPU is a second way to write a model (DistributedWorker plus a hand-driven WorkerGroup) alongside ReactorModel/ReactorPipeline. I'd rather it be a capability of the existing lineage. Here's the bar. Taking a single-GPU model to two GPUs should be a small in-place change: read self.rank/self.world_size, guard the emit with self.is_leader, shard a tensor.

Distributed by default, single-GPU is world_size=1:

  • One class in the existing lineage. The runtime injects rank/world_size/device/is_leader, defaulting to a world of one. A model that never shards never reads them.
  • world_size=1 runs in-process. No spawn, no shared-memory round-trip, today's latency. The spawn and shared-memory path is for world_size > 1 only.
  • world_size comes from config/manifest, not hardcoded in load(). Device count is a deployment fact like fps. RuntimeConfig already reaches the runner whole, and config_path is the analogue.
  • WorkerGroup, SharedFrameBuffer, and the protocol stay as internals the base class drives, off the package root. Exposing SharedFrameBuffer pins one transport. Exposing WorkerGroup commits the hand-driven shape.
  • Mark the surface experimental so we can refine it without a break.

The base class is the paved road that covers essentially every model. The raw primitives stay available as experimental internals for the rare model that genuinely needs to hand-drive. That demotes them from "the API" to "the escape hatch" instead of removing the capability.

Think about DX ergonomics when deciding to either keep generate_chunk(index, controls), or converge on inference() so single-GPU is literally today's pipeline at world_size=1. I lean inference(), though there's a real trade with the shared-memory writes.

The README documents DistributedWorker/WorkerGroup as public API. Repoint it at the base-class and injected-context shape, and flag it experimental.

This is moving the driving into a base class and thinning the surface.

If my recommendations are wildly out of left field or otherwise would pose too many ergonomic challenges, please let me know.

maxmb1 and others added 2 commits September 1, 2026 15:54
Review follow-up (#134): the distributed primitives are the raw layer a
future runtime-managed path will drive, so they should not harden into
the stable top-level API before that path exists.

- Drop the five distributed re-exports from the reactor_runtime root;
  they are imported from reactor_runtime.distributed only, and the
  package test now asserts they stay off the root rather than on it.
- Mark the surface experimental in the package docstring and README,
  and state the direction: the runtime owns the group wiring, the
  primitives remain the escape hatch for layouts the managed path
  cannot express.
- Recommend sourcing world_size from model config instead of a literal
  in load().

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Kavya <kavya@reactor.inc>
Provide an experimental video adapter with managed worker sessions, control snapshots, pause/reset, and cleanup. Derive worker count from the manifest, keep one-worker execution in-process, and include a runnable example with actionable errors.

Include current main while preserving the original PR commits.

Signed-off-by: Kavya <58096384+maxmb1@users.noreply.github.com>

maxmb1 commented Sep 9, 2026

Copy link
Copy Markdown

## Why

Multi-GPU models need per-rank lifecycle hooks and bounded frame transport without committing the package root to a stable distributed API. This is the foundation of the REA-5060 stack and can land independently of an engine host.

## What Changed

Adds the experimental single-node DistributedWorker protocol, worker error types, and shared uint8 video frame transport. Torch remains lazy and optional at import time, and package tests keep the surface off the root exports. Frame-buffer and package tests accompany this layer; worker-group orchestration, the managed model adapter, and the runnable example are separate dependent PRs. Local Python 3.12 unit/contract tests, lint, and type checking pass. No NVIDIA/NCCL execution is claimed.

Signed-off-by: Kavya <58096384+maxmb1@users.noreply.github.com>
@maxmb1 maxmb1 changed the title [REA-5060] Give a model a way to run on more than one GPU [REA-5060] Add distributed worker protocol and frame transport Sep 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants