reactor-runtime 3.5.0__tar.gz → 3.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/PKG-INFO +5 -4
  2. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/README.md +4 -3
  3. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/pyproject.toml +1 -1
  4. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/pyproject.toml.orig +1 -1
  5. reactor_runtime-3.6.0/src/reactor_runtime/distributed/__init__.py +34 -0
  6. reactor_runtime-3.6.0/src/reactor_runtime/distributed/errors.py +43 -0
  7. reactor_runtime-3.6.0/src/reactor_runtime/distributed/ipc.py +227 -0
  8. reactor_runtime-3.6.0/src/reactor_runtime/distributed/protocol.py +66 -0
  9. reactor_runtime-3.6.0/src/reactor_runtime/distributed/rank.py +246 -0
  10. reactor_runtime-3.6.0/src/reactor_runtime/distributed/runner.py +387 -0
  11. reactor_runtime-3.6.0/src/reactor_runtime/distributed/worker.py +74 -0
  12. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/recording/chunk_encoder.py +34 -11
  13. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/recording/recorder.py +54 -14
  14. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/LICENSE +0 -0
  15. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/NOTICE +0 -0
  16. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/__init__.py +0 -0
  17. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/codes.py +0 -0
  18. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/core/__init__.py +0 -0
  19. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/core/fields.py +0 -0
  20. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/core/model.py +0 -0
  21. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/core/naming.py +0 -0
  22. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/core/service.py +0 -0
  23. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/core/session.py +0 -0
  24. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/core/transport.py +0 -0
  25. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/core/typespec.py +0 -0
  26. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/core/values.py +0 -0
  27. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/event_stream.py +0 -0
  28. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/http/__init__.py +0 -0
  29. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/http/events.py +0 -0
  30. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/http/routes.py +0 -0
  31. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/http/server.py +0 -0
  32. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/http/spec.py +0 -0
  33. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/interface/__init__.py +0 -0
  34. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/interface/app/__init__.py +0 -0
  35. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/interface/app/input_state.py +0 -0
  36. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/interface/app/outcome.py +0 -0
  37. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/interface/app/reactor_app.py +0 -0
  38. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/interface/client.py +0 -0
  39. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/interface/events/__init__.py +0 -0
  40. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/interface/events/decorators.py +0 -0
  41. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/interface/events/errors.py +0 -0
  42. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/interface/events/messages.py +0 -0
  43. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/interface/internal/__init__.py +0 -0
  44. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/interface/internal/aliases.py +0 -0
  45. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/interface/internal/bridge.py +0 -0
  46. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/interface/internal/input_buffer.py +0 -0
  47. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/interface/internal/reactor_core.py +0 -0
  48. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/interface/model/__init__.py +0 -0
  49. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/interface/model/contract.py +0 -0
  50. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/interface/model/reactor_model.py +0 -0
  51. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/interface/model/schema.py +0 -0
  52. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/interface/pipeline/__init__.py +0 -0
  53. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/interface/pipeline/idle.py +0 -0
  54. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/interface/pipeline/input_state.py +0 -0
  55. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/interface/pipeline/reactor_pipeline.py +0 -0
  56. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/interface/tracks/__init__.py +0 -0
  57. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/interface/tracks/descriptors.py +0 -0
  58. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/interface/tracks/input.py +0 -0
  59. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/interface/tracks/output.py +0 -0
  60. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/log.py +0 -0
  61. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/manifest.py +0 -0
  62. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/message_gateway.py +0 -0
  63. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/metrics/__init__.py +0 -0
  64. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/metrics/command.py +0 -0
  65. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/metrics/model.py +0 -0
  66. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/metrics/registry.py +0 -0
  67. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/metrics/session.py +0 -0
  68. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/paths.py +0 -0
  69. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/protocol/__init__.py +0 -0
  70. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/protocol/base.py +0 -0
  71. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/protocol/common.py +0 -0
  72. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/protocol/v0/__init__.py +0 -0
  73. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/protocol/v0/codec.py +0 -0
  74. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/protocol/v1/__init__.py +0 -0
  75. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/protocol/v1/codec.py +0 -0
  76. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/py.typed +0 -0
  77. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/recording/__init__.py +0 -0
  78. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/recording/markers.py +0 -0
  79. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/runner/__init__.py +0 -0
  80. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/runner/connection_manager.py +0 -0
  81. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/runner/offer_epochs.py +0 -0
  82. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/runner/runner.py +0 -0
  83. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/runner/state_machine.py +0 -0
  84. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/runner/upload_resolution.py +0 -0
  85. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/schema.py +0 -0
  86. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/serve.py +0 -0
  87. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/service.py +0 -0
  88. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/transport/__init__.py +0 -0
  89. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/transport/acceptor.py +0 -0
  90. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/transport/router.py +0 -0
  91. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/transport/webrtc/__init__.py +0 -0
  92. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/transport/webrtc/acceptor.py +0 -0
  93. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/transport/webrtc/config.py +0 -0
  94. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/transport/webrtc/connection.py +0 -0
  95. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/transport/webrtc/frames.py +0 -0
  96. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/transport/webrtc/metrics.py +0 -0
  97. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/transport/webrtc/pacer.py +0 -0
  98. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/transport/webrtc/peer.py +0 -0
  99. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/transport/webrtc/router.py +0 -0
  100. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/transport/webrtc/sdp.py +0 -0
  101. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/transport/webrtc/signaling.py +0 -0
  102. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/transport/webrtc/stats.py +0 -0
  103. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/transport/webrtc/version.py +0 -0
  104. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_runtime/upload_store.py +0 -0
  105. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_wire/v1/common_pb2.py +0 -0
  106. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_wire/v1/common_pb2.pyi +0 -0
  107. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_wire/v1/control_pb2.py +0 -0
  108. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_wire/v1/control_pb2.pyi +0 -0
  109. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_wire/v1/data_pb2.py +0 -0
  110. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_wire/v1/data_pb2.pyi +0 -0
  111. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_wire/v1/model_pb2.py +0 -0
  112. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_wire/v1/model_pb2.pyi +0 -0
  113. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_wire/v1/platform_pb2.py +0 -0
  114. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_wire/v1/platform_pb2.pyi +0 -0
  115. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_wire/v1/track_pb2.py +0 -0
  116. {reactor_runtime-3.5.0 → reactor_runtime-3.6.0}/src/reactor_wire/v1/track_pb2.pyi +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: reactor-runtime
3
- Version: 3.5.0
3
+ Version: 3.6.0
4
4
  Summary: A Python framework for building real-time, interactive video models
5
5
  Author: Reactor
6
6
  Author-email: Reactor <team@reactor.inc>
@@ -30,7 +30,7 @@ Description-Content-Type: text/markdown
30
30
 
31
31
  **Build real-time AI models in Python.**
32
32
 
33
- [📖 Documentation](https://docs.reactor.inc/deploy/overview) · [🚀 Quickstart](https://docs.reactor.inc/deploy/development/overview) · [🌐 Reactor](https://reactor.inc)
33
+ [📖 Documentation](https://docs.reactor.inc/deploy/overview) · [🚀 Quickstart](https://docs.reactor.inc/deploy/development/quickstart) · [🌐 Reactor](https://reactor.inc)
34
34
 
35
35
  </div>
36
36
 
@@ -129,7 +129,7 @@ cd my-model
129
129
  reactor run
130
130
  ```
131
131
 
132
- `reactor run` builds a container with the runtime inside and serves WebRTC signaling on port 8080. Point a browser at it with the [JS SDK](https://docs.reactor.inc), or connect from the [Reactor Sandbox](https://reactor-sandbox.vercel.app/) and watch frames stream immediately.
132
+ `reactor run` builds a container with the runtime inside and serves WebRTC signaling on port 8080. Point a browser at it with the [JS SDK](https://docs.reactor.inc/sdk-reference/using-the-sdk), or connect from the [Reactor Sandbox](https://reactor-sandbox.vercel.app/) and watch frames stream immediately.
133
133
 
134
134
  Log from the same import, passing context as keyword arguments:
135
135
 
@@ -155,7 +155,8 @@ Not on macOS, or pinning a release in CI? See [Install the CLI](https://docs.rea
155
155
 
156
156
  ## Learn more
157
157
 
158
- - [Quickstart](https://docs.reactor.inc/deploy/development/overview): from zero to a streaming model in 2 minutes
158
+ - [Quickstart](https://docs.reactor.inc/deploy/development/quickstart): from zero to a model deployed on Reactor's GPUs
159
+ - [Runtime overview](https://docs.reactor.inc/deploy/development/overview): what the runtime handles, and the outline of a model
159
160
  - [Model anatomy](https://docs.reactor.inc/deploy/development/reactor-app/model-anatomy): every member of a `ReactorApp`, line by line
160
161
  - [The step loop](https://docs.reactor.inc/deploy/development/reactor-app/step-loop): `process_input()`, `generate()`, `process_output()`, and the frame rate
161
162
  - [Starter example](./examples/starter/README.md): the model `reactor init` scaffolds: one class, `generate()` alone, the smallest complete `ReactorApp`
@@ -4,7 +4,7 @@
4
4
 
5
5
  **Build real-time AI models in Python.**
6
6
 
7
- [📖 Documentation](https://docs.reactor.inc/deploy/overview) · [🚀 Quickstart](https://docs.reactor.inc/deploy/development/overview) · [🌐 Reactor](https://reactor.inc)
7
+ [📖 Documentation](https://docs.reactor.inc/deploy/overview) · [🚀 Quickstart](https://docs.reactor.inc/deploy/development/quickstart) · [🌐 Reactor](https://reactor.inc)
8
8
 
9
9
  </div>
10
10
 
@@ -103,7 +103,7 @@ cd my-model
103
103
  reactor run
104
104
  ```
105
105
 
106
- `reactor run` builds a container with the runtime inside and serves WebRTC signaling on port 8080. Point a browser at it with the [JS SDK](https://docs.reactor.inc), or connect from the [Reactor Sandbox](https://reactor-sandbox.vercel.app/) and watch frames stream immediately.
106
+ `reactor run` builds a container with the runtime inside and serves WebRTC signaling on port 8080. Point a browser at it with the [JS SDK](https://docs.reactor.inc/sdk-reference/using-the-sdk), or connect from the [Reactor Sandbox](https://reactor-sandbox.vercel.app/) and watch frames stream immediately.
107
107
 
108
108
  Log from the same import, passing context as keyword arguments:
109
109
 
@@ -129,7 +129,8 @@ Not on macOS, or pinning a release in CI? See [Install the CLI](https://docs.rea
129
129
 
130
130
  ## Learn more
131
131
 
132
- - [Quickstart](https://docs.reactor.inc/deploy/development/overview): from zero to a streaming model in 2 minutes
132
+ - [Quickstart](https://docs.reactor.inc/deploy/development/quickstart): from zero to a model deployed on Reactor's GPUs
133
+ - [Runtime overview](https://docs.reactor.inc/deploy/development/overview): what the runtime handles, and the outline of a model
133
134
  - [Model anatomy](https://docs.reactor.inc/deploy/development/reactor-app/model-anatomy): every member of a `ReactorApp`, line by line
134
135
  - [The step loop](https://docs.reactor.inc/deploy/development/reactor-app/step-loop): `process_input()`, `generate()`, `process_output()`, and the frame rate
135
136
  - [Starter example](./examples/starter/README.md): the model `reactor init` scaffolds: one class, `generate()` alone, the smallest complete `ReactorApp`
@@ -95,7 +95,7 @@ pythonpath = ["."]
95
95
 
96
96
  [project]
97
97
  name = "reactor-runtime"
98
- version = "3.5.0"
98
+ version = "3.6.0"
99
99
  description = "A Python framework for building real-time, interactive video models"
100
100
  readme = "README.md"
101
101
  license = "Apache-2.0"
@@ -20,7 +20,7 @@ version = "1.20260814.7"
20
20
 
21
21
  [project]
22
22
  name = "reactor-runtime"
23
- version = "3.5.0"
23
+ version = "3.6.0"
24
24
  description = "A Python framework for building real-time, interactive video models"
25
25
  readme = "README.md"
26
26
  license = "Apache-2.0"
@@ -0,0 +1,34 @@
1
+ """Run a model in its own process, or in one process per GPU, behind one object.
2
+
3
+ A worker is a class with ``load()``, ``generate()``, and ``reset()``. A
4
+ :class:`DistributedRunner` starts N of them, one per GPU, and lets the caller
5
+ drive all of them with one call. The caller writes no process, queue,
6
+ shared-memory, or NCCL code::
7
+
8
+ runner = DistributedRunner(MyModel, load_kwargs={"config_path": path})
9
+ runner.start()
10
+ result = runner.generate(MyInput(...))
11
+ runner.reset()
12
+ runner.shutdown()
13
+
14
+ This package is standalone. Nothing else in the runtime depends on it, and it
15
+ reads no manifest and no environment variable of its own.
16
+ """
17
+
18
+ from reactor_runtime.distributed.errors import (
19
+ RankDesync,
20
+ SharedSlotAllocationFailed,
21
+ WorkerCrashed,
22
+ WorkerTimeout,
23
+ )
24
+ from reactor_runtime.distributed.runner import DistributedRunner
25
+ from reactor_runtime.distributed.worker import DistributedWorker
26
+
27
+ __all__ = [
28
+ "DistributedRunner",
29
+ "DistributedWorker",
30
+ "RankDesync",
31
+ "SharedSlotAllocationFailed",
32
+ "WorkerCrashed",
33
+ "WorkerTimeout",
34
+ ]
@@ -0,0 +1,43 @@
1
+ """Failures a :class:`~reactor_runtime.distributed.DistributedRunner` raises to its caller.
2
+
3
+ A worker's own exception is never wrapped: when every rank raises the same
4
+ error, the caller receives rank 0's instance unchanged. The types here cover
5
+ what the runner itself detects.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+
11
+ class WorkerCrashed(RuntimeError): # noqa: N818 (named for the event a caller catches)
12
+ """A worker process died without answering.
13
+
14
+ The message names the rank and its exit code. The group is unusable after
15
+ this; ``shutdown()`` ends it.
16
+ """
17
+
18
+
19
+ class WorkerTimeout(TimeoutError): # noqa: N818 (named for the event a caller catches)
20
+ """A call did not get an answer from every rank within its timeout.
21
+
22
+ The processes are alive but one of them is stuck, usually inside a
23
+ collective a peer abandoned. The group is unusable after this.
24
+ """
25
+
26
+
27
+ class RankDesync(RuntimeError): # noqa: N818 (named for the state a caller catches)
28
+ """Some ranks raised and others did not, or ranks raised different types of error.
29
+
30
+ The message names the first failing rank and carries its exception as
31
+ ``__cause__``. Rank 0's result, if any, came out of a collective a failing
32
+ rank was meant to contribute to, so it is not returned. The group refuses
33
+ further calls.
34
+ """
35
+
36
+
37
+ class SharedSlotAllocationFailed(MemoryError): # noqa: N818 (named for the event a caller catches)
38
+ """A shared-memory block could not be created or grown.
39
+
40
+ The message states the size that was asked for and, where the host exposes
41
+ it, the size of ``/dev/shm``. Docker's default is 64 MB; ``--shm-size`` on
42
+ ``docker run`` and a ``medium: Memory`` ``emptyDir`` on a pod raise it.
43
+ """
@@ -0,0 +1,227 @@
1
+ """Carry an author's object across a process boundary without copying it twice.
2
+
3
+ :func:`pack` pickles an object with protocol 5. Every flat buffer the object
4
+ holds, a contiguous numpy array above all, is handed to the writer's
5
+ :class:`SharedSlot` instead of being written into the pickle. The pickle
6
+ stays small and travels on a queue; the bytes sit in shared memory. The
7
+ header names the block, so the reader attaches to whatever it is told and
8
+ the block can grow without a protocol of its own.
9
+
10
+ Every block a slot creates is named from the slot's prefix and a generation
11
+ number. A process that knows the prefix can unlink what a writer left behind
12
+ when the writer was terminated before it could close its slot.
13
+
14
+ A torch tensor and a non-contiguous array do not expose a flat buffer and
15
+ pickle inline. That still works, only slower, and is reported once per
16
+ process when the header passes about one megabyte.
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import contextlib
22
+ import pickle
23
+ import secrets
24
+ import shutil
25
+ from multiprocessing import shared_memory
26
+ from pathlib import Path
27
+ from typing import Any
28
+
29
+ from reactor_runtime.distributed.errors import SharedSlotAllocationFailed
30
+ from reactor_runtime.log import get_logger
31
+
32
+ logger = get_logger(__name__)
33
+
34
+ _INITIAL_BYTES = 1 << 20
35
+ _INLINE_WARN_BYTES = 1 << 20
36
+ _SHM_DIR = Path("/dev/shm")
37
+ # Growth at least doubles a block, so no slot reaches this many generations.
38
+ _MAX_GENERATIONS = 64
39
+
40
+ _inline_reported = False
41
+
42
+
43
+ def new_prefix() -> str:
44
+ """Return a block-name prefix for one slot. Short enough for macOS's 31-character limit."""
45
+ return f"rr{secrets.token_hex(4)}_"
46
+
47
+
48
+ class SharedSlot:
49
+ """One shared-memory block a writer fills, one call at a time.
50
+
51
+ A call starts with :meth:`begin`, appends buffers with :meth:`write`, and
52
+ hands the returned spans to the reader inside the header. When a call's
53
+ buffers do not fit, the block is replaced by a larger one and the bytes
54
+ already written move with it. Steady state is one block at the writer's
55
+ high-water mark.
56
+
57
+ Args:
58
+ prefix: Names every block this slot creates, as ``<prefix><generation>``.
59
+ A process that holds the prefix can reclaim the blocks with
60
+ :func:`unlink_blocks` if the writer dies without :meth:`close`.
61
+ A fresh one from :func:`new_prefix` when omitted.
62
+ initial_bytes: The size of the first block.
63
+ """
64
+
65
+ def __init__(self, *, prefix: str | None = None, initial_bytes: int = _INITIAL_BYTES) -> None:
66
+ self._prefix = prefix or new_prefix()
67
+ self._generation = 0
68
+ self._shm = _allocate(f"{self._prefix}0", initial_bytes)
69
+ self._cursor = 0
70
+
71
+ @property
72
+ def name(self) -> str:
73
+ """The name a reader attaches to. Changes when the block grows."""
74
+ return self._shm.name
75
+
76
+ @property
77
+ def capacity(self) -> int:
78
+ """Bytes the current block holds."""
79
+ return self._shm.size
80
+
81
+ @property
82
+ def _buf(self) -> memoryview:
83
+ return _buffer_of(self._shm)
84
+
85
+ def begin(self) -> None:
86
+ """Start a new call; the next write lands at offset zero."""
87
+ self._cursor = 0
88
+
89
+ def write(self, buffer: Any) -> tuple[int, int]:
90
+ """Append one flat buffer and return its ``(offset, length)`` span."""
91
+ if isinstance(buffer, pickle.PickleBuffer):
92
+ raw = buffer.raw()
93
+ else:
94
+ raw = memoryview(buffer).cast("B")
95
+ length = raw.nbytes
96
+ end = self._cursor + length
97
+ if end > self.capacity:
98
+ self._grow(end)
99
+ self._buf[self._cursor : end] = raw
100
+ span = (self._cursor, length)
101
+ self._cursor = end
102
+ return span
103
+
104
+ def close(self) -> None:
105
+ """Detach and unlink the block."""
106
+ with contextlib.suppress(Exception):
107
+ self._shm.close()
108
+ with contextlib.suppress(Exception):
109
+ self._shm.unlink()
110
+
111
+ def _grow(self, needed: int) -> None:
112
+ self._generation += 1
113
+ replacement = _allocate(f"{self._prefix}{self._generation}", max(needed, 2 * self.capacity))
114
+ _buffer_of(replacement)[: self._cursor] = self._buf[: self._cursor]
115
+ logger.info("shared block grown", from_bytes=self.capacity, to_bytes=replacement.size)
116
+ self.close()
117
+ self._shm = replacement
118
+
119
+
120
+ class SlotReader:
121
+ """The reading side: attaches to the block a header names and keeps the mapping."""
122
+
123
+ def __init__(self) -> None:
124
+ self._shm: shared_memory.SharedMemory | None = None
125
+
126
+ def attach(self, name: str) -> None:
127
+ """Map the block called *name*, replacing the previous mapping if it differs."""
128
+ if self._shm is not None and self._shm.name == name:
129
+ return
130
+ self.close()
131
+ self._shm = shared_memory.SharedMemory(name=name)
132
+
133
+ def copy(self, offset: int, length: int) -> bytearray:
134
+ """Copy one span out. A copy, so the writer may reuse the block at once."""
135
+ if self._shm is None:
136
+ raise RuntimeError("attach() has not run")
137
+ return bytearray(_buffer_of(self._shm)[offset : offset + length])
138
+
139
+ def close(self) -> None:
140
+ """Drop the mapping. The writer owns the block and unlinks it."""
141
+ if self._shm is not None:
142
+ with contextlib.suppress(Exception):
143
+ self._shm.close()
144
+ self._shm = None
145
+
146
+
147
+ def pack(obj: Any, slot: SharedSlot) -> bytes:
148
+ """Pickle *obj* with its flat buffers written into *slot*.
149
+
150
+ Returns:
151
+ A header for the queue: the pickle bytes, the block's name, and the
152
+ span of each out-of-band buffer, pickled together.
153
+ """
154
+ slot.begin()
155
+ spans: list[tuple[int, int]] = []
156
+
157
+ def out_of_band(buffer: pickle.PickleBuffer) -> None:
158
+ spans.append(slot.write(buffer))
159
+
160
+ blob = pickle.dumps(obj, protocol=5, buffer_callback=out_of_band)
161
+ if len(blob) > _INLINE_WARN_BYTES:
162
+ _report_inline(len(blob))
163
+ return pickle.dumps((blob, slot.name, spans), protocol=5)
164
+
165
+
166
+ def unpack(header: bytes, reader: SlotReader) -> Any:
167
+ """Rebuild the object a :func:`pack` header describes."""
168
+ blob, name, spans = pickle.loads(header)
169
+ reader.attach(name)
170
+ return pickle.loads(blob, buffers=[reader.copy(offset, length) for offset, length in spans])
171
+
172
+
173
+ def unlink_blocks(prefix: str) -> None:
174
+ """Unlink every block a :class:`SharedSlot` with *prefix* left behind.
175
+
176
+ For a writer that ended without closing its slot, such as a terminated or
177
+ killed process. Every generation the slot could have reached is tried, so
178
+ a block that grew after its last header was read is reclaimed too. Call it
179
+ only once the writer is dead: a live writer's block would go with the rest.
180
+ """
181
+ for generation in range(_MAX_GENERATIONS):
182
+ try:
183
+ shm = shared_memory.SharedMemory(name=f"{prefix}{generation}")
184
+ except FileNotFoundError:
185
+ continue
186
+ with contextlib.suppress(Exception):
187
+ shm.close()
188
+ with contextlib.suppress(Exception):
189
+ shm.unlink()
190
+
191
+
192
+ def _allocate(name: str, size: int) -> shared_memory.SharedMemory:
193
+ if _SHM_DIR.exists():
194
+ usage = shutil.disk_usage(_SHM_DIR)
195
+ if size > usage.free:
196
+ raise SharedSlotAllocationFailed(
197
+ f"could not grow the shared buffer to {size / 2**20:.1f} MB. /dev/shm has "
198
+ f"{usage.total / 2**20:.1f} MB total and {usage.free / 2**20:.1f} MB free. "
199
+ "Run with a larger --shm-size, or size /dev/shm in the deployment."
200
+ )
201
+ try:
202
+ return shared_memory.SharedMemory(name=name, create=True, size=size)
203
+ except OSError as exc:
204
+ raise SharedSlotAllocationFailed(
205
+ f"could not allocate a shared buffer of {size / 2**20:.1f} MB: {exc}. "
206
+ "Run with a larger --shm-size, or size /dev/shm in the deployment."
207
+ ) from exc
208
+
209
+
210
+ def _buffer_of(shm: shared_memory.SharedMemory) -> memoryview:
211
+ buf = shm.buf
212
+ if buf is None:
213
+ raise RuntimeError(f"shared block {shm.name} is closed")
214
+ return buf
215
+
216
+
217
+ def _report_inline(size: int) -> None:
218
+ global _inline_reported
219
+ if _inline_reported:
220
+ return
221
+ _inline_reported = True
222
+ logger.warning(
223
+ "a call's header is large; some payload pickled inline instead of through shared memory. "
224
+ "A torch tensor or a non-contiguous array does that: pass np.ascontiguousarray(x) or "
225
+ "np.asarray(tensor.cpu())",
226
+ header_bytes=size,
227
+ )
@@ -0,0 +1,66 @@
1
+ """The messages between a runner and its ranks.
2
+
3
+ Requests go from the runner to every rank, in the same order on every rank's
4
+ queue. Every rank answers every request. A large payload never rides on a
5
+ queue: :class:`Generate` and :class:`Answer` carry a header from
6
+ :func:`~reactor_runtime.distributed.ipc.pack`, and the bytes sit in a shared
7
+ block the header names.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from dataclasses import dataclass
13
+
14
+
15
+ @dataclass(frozen=True)
16
+ class Generate:
17
+ """Run one ``generate()`` on every rank with the input the header describes."""
18
+
19
+ header: bytes
20
+
21
+
22
+ @dataclass(frozen=True)
23
+ class Reset:
24
+ """Call ``reset()`` on every rank."""
25
+
26
+
27
+ @dataclass(frozen=True)
28
+ class Shutdown:
29
+ """Leave the request loop, run the exit barrier, and end the process."""
30
+
31
+
32
+ @dataclass(frozen=True)
33
+ class Rendezvous:
34
+ """Rank 0 listens for the process group on this port, which it bound itself."""
35
+
36
+ port: int
37
+
38
+
39
+ @dataclass(frozen=True)
40
+ class Join:
41
+ """Join the process group on the port rank 0 reported. Sent to every other rank."""
42
+
43
+ port: int
44
+
45
+
46
+ @dataclass(frozen=True)
47
+ class Loaded:
48
+ """A rank finished ``load()`` and is ready for requests."""
49
+
50
+ rank: int
51
+
52
+
53
+ @dataclass(frozen=True)
54
+ class Answer:
55
+ """One rank's reply to a :class:`Generate` or :class:`Reset`.
56
+
57
+ Attributes:
58
+ rank: The answering rank.
59
+ header: Rank 0's packed result on a successful ``Generate``. Other
60
+ ranks and every ``Reset`` send ``None``.
61
+ error: What the worker raised, or ``None`` on success.
62
+ """
63
+
64
+ rank: int
65
+ header: bytes | None = None
66
+ error: Exception | None = None