|
40 | 40 | "Collate", |
41 | 41 | "DisaggregateConfig", |
42 | 42 | "_SubprocessPipelineConfig", |
| 43 | + "_MainProcess", |
| 44 | + "ExecutorConfig", |
| 45 | + "ProcessPoolExecutorConfig", |
| 46 | + "InterpreterPoolExecutorConfig", |
| 47 | + "MAIN_PROCESS", |
| 48 | + "PlacementConfig", |
43 | 49 | "PipelineConfig", |
44 | 50 | "SinkConfig", |
45 | 51 | "SourceConfig", |
@@ -436,6 +442,110 @@ class _SubprocessPipelineConfig: |
436 | 442 | """The picklable submit-side handle (``_SubprocessPipelineHandle``) for the worker pool.""" |
437 | 443 |
|
438 | 444 |
|
| 445 | +################################################################################ |
| 446 | +# Executor placement (region markers for `.to()`) |
| 447 | +################################################################################ |
| 448 | + |
| 449 | + |
| 450 | +@dataclass(frozen=True) |
| 451 | +class ExecutorConfig: |
| 452 | + """**[Experimental]** Base spec for a worker-pool executor. |
| 453 | +
|
| 454 | + A serializable description of a pool of workers, materialized into a live executor by |
| 455 | + the pipeline. Subclassed by :py:class:`ProcessPoolExecutorConfig` and |
| 456 | + :py:class:`InterpreterPoolExecutorConfig`; those subclasses are used as ``.to()`` |
| 457 | + placement targets (via :py:class:`PlacementConfig`). |
| 458 | +
|
| 459 | + .. versionadded:: 0.6.0 |
| 460 | + """ |
| 461 | + |
| 462 | + max_workers: int | None = None |
| 463 | + """Number of workers. If ``None``, defaults to the number of CPUs.""" |
| 464 | + |
| 465 | + initializer: Callable[..., object] | None = None |
| 466 | + """Callable run once in each worker before it processes any work.""" |
| 467 | + |
| 468 | + initargs: tuple[Any, ...] = () |
| 469 | + """Positional arguments passed to ``initializer``.""" |
| 470 | + |
| 471 | + |
| 472 | +@dataclass(frozen=True) |
| 473 | +class ProcessPoolExecutorConfig(ExecutorConfig): |
| 474 | + """**[Experimental]** A worker-pool executor backed by subprocesses. |
| 475 | +
|
| 476 | + Used as a ``.to()`` target to run a region's stages in a pool of worker *processes* |
| 477 | + as one nested pipeline — the op->op handoff stays in the worker, so intermediate |
| 478 | + values are not copied back to the main process and need not be picklable. The region |
| 479 | + ends at the next ``.to()`` target (see :py:data:`MAIN_PROCESS`). |
| 480 | +
|
| 481 | + .. versionadded:: 0.6.0 |
| 482 | + """ |
| 483 | + |
| 484 | + mp_context: str | None = None |
| 485 | + """Multiprocessing start method (e.g. ``"spawn"``, ``"fork"``, ``"forkserver"``), |
| 486 | + as accepted by :py:func:`multiprocessing.get_context`. ``None`` uses the default |
| 487 | + context.""" |
| 488 | + |
| 489 | + |
| 490 | +@dataclass(frozen=True) |
| 491 | +class InterpreterPoolExecutorConfig(ExecutorConfig): |
| 492 | + """**[Experimental]** A worker-pool executor backed by subinterpreters. |
| 493 | +
|
| 494 | + Like :py:class:`ProcessPoolExecutorConfig`, but the workers are Python |
| 495 | + *subinterpreters* (:py:mod:`concurrent.interpreters`) sharing the process rather than |
| 496 | + separate processes. There is no ``mp_context`` because no new process is started. |
| 497 | +
|
| 498 | + .. note:: |
| 499 | +
|
| 500 | + Requires Python 3.14 or later; using this target on an older interpreter is an |
| 501 | + error. NumPy and PyTorch cannot be imported inside a subinterpreter, so a region |
| 502 | + whose stages need them must use :py:class:`ProcessPoolExecutorConfig` instead. |
| 503 | +
|
| 504 | + .. versionadded:: 0.6.0 |
| 505 | + """ |
| 506 | + |
| 507 | + |
| 508 | +@dataclass(frozen=True) |
| 509 | +class _MainProcess: |
| 510 | + """Sentinel ``.to()`` target for the main process. Use the :py:data:`MAIN_PROCESS` |
| 511 | + singleton rather than instantiating this type.""" |
| 512 | + |
| 513 | + def __repr__(self) -> str: |
| 514 | + return "MAIN_PROCESS" |
| 515 | + |
| 516 | + |
| 517 | +MAIN_PROCESS: _MainProcess = _MainProcess() |
| 518 | +"""**[Experimental]** The main-process execution target. Pass to |
| 519 | +:py:meth:`spdl.pipeline.PipelineBuilder.to` |
| 520 | +to close a worker-pool region and bring subsequent stages back to the main process. |
| 521 | +
|
| 522 | +.. versionadded:: 0.6.0 |
| 523 | +""" |
| 524 | + |
| 525 | + |
| 526 | +@dataclass(frozen=True) |
| 527 | +class PlacementConfig: |
| 528 | + """**[Experimental]** A region marker designating where the subsequent stages execute. |
| 529 | +
|
| 530 | + Sits among the stage configs in :py:attr:`PipelineConfig.pipes`: every stage after |
| 531 | + this marker (until the next :py:class:`PlacementConfig`) runs on :py:attr:`target`. |
| 532 | + :py:meth:`spdl.pipeline.PipelineBuilder.to` appends one of these. A pipeline |
| 533 | + implicitly starts on the main process, so a marker is needed only to enter a |
| 534 | + worker-pool region and (with :py:data:`MAIN_PROCESS`) to leave it. |
| 535 | +
|
| 536 | + .. versionadded:: 0.6.0 |
| 537 | + """ |
| 538 | + |
| 539 | + target: "ProcessPoolExecutorConfig | InterpreterPoolExecutorConfig | _MainProcess" |
| 540 | + """Where the stages following this marker execute.""" |
| 541 | + |
| 542 | + name: str = "placement" |
| 543 | + """Name of the marker (used only for display).""" |
| 544 | + |
| 545 | + def __repr__(self) -> str: |
| 546 | + return f"{self.name}({self.target!r})" |
| 547 | + |
| 548 | + |
439 | 549 | ################################################################################ |
440 | 550 | # PathVariants |
441 | 551 | ################################################################################ |
@@ -607,6 +717,7 @@ class PipelineConfig(Generic[U]): |
607 | 717 | | DisaggregateConfig[Any] |
608 | 718 | | PathVariantsConfig[Any] |
609 | 719 | | _SubprocessPipelineConfig |
| 720 | + | PlacementConfig |
610 | 721 | ] |
611 | 722 | """Pipe configurations.""" |
612 | 723 |
|
|
0 commit comments