Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .agents/skills/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,13 @@ tooling without maintaining duplicate copies.

Add or update skill directories here. Each skill directory must contain its own
`SKILL.md`.

## Skills

| Skill | Description |
|---|---|
| `create-feature-request` | File a GitHub feature request following the repo template |
| `create-issue` | File a GitHub bug or docs issue following the repo template |
| `rebase-on-main` | Rebase the current feature branch on latest main and force-push, resolving conflicts by preserving the intent of both sides |
| `retrigger-ci` | Retry a failing CI run until it passes when the failure is flaky and unrelated to our changes |
| `platform-style-fix` | Check and fix clang-format style violations in `platform/include` and `platform/examples` |
16 changes: 11 additions & 5 deletions platform/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,27 @@ platform/
build/ generated build output; not documented here
```

## Module Areas
## Documentation

- [Architecture](architecture.md): instance roles, module system, channel model, and scope.
- [Simpler Integration](simpler-integration.md): how replica ranks launch Simpler, the `processFc` interface, and the path to in-device tensor channels.
- [Model Support Interface](model-support-interface.md): what Model Support owns, provides, and will consume from the platform.
- [System](system/README.md): engine lifecycle and cross-instance start/stop control.
- [Channels](system/channels.md): payload, coordination, metadata, input, output, and message primitives.

Configuration types, the service module, channel controller, and broadcast deployment are tracked in issue #32 and are not part of this initial PR.
- [Modules](modules/README.md): configuration, service, channel controller, and broadcast deployment.

## Runtime Shape

The current platform runtime is built around `serving::system::Engine`. The engine owns a set of `serving::modules::Module` instances, initializes them, starts them across instances through RPC, waits for termination, and finalizes them.

This initial PR covers the following building blocks:
This PR covers the following building blocks:

- Engine lifecycle: cross-instance start/stop over RPC (`serving::system::Engine`).
- Module base interface: initialize/run/terminate/await/finalize lifecycle with optional `taskr::Service` (`serving::modules::Module`).
- Channel primitives: `Input`, `Output`, `Message`, and `MessageTypeRegistry` for host-side control traffic.
- Configuration types: `Deployment`, `Partition`, `Task`, `Edge`, `Replica`, `RequestManager` with JSON serialization and verification.
- `broadcastDeployment`: deployer-to-worker deployment config distribution over RPC.
- `channelController`: desired-vs-actual reconciliation loop for host-side SPSC channels.
- `service`: wraps `taskr::Runtime` for cooperative background services.

Deployment graph representation, deployment broadcast, desired-state channel creation, dynamic scaling, topology-aware replacement, fault recovery, and Python bindings are not implemented in this PR.
Dynamic scaling, fault recovery, `channelDispatcher`, `taskScheduler`, executor roles, heartbeat, `RuntimePlan` update protocol, in-device tensor channels, and Python bindings are deferred to follow-up PRs.
69 changes: 69 additions & 0 deletions platform/docs/architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Platform Architecture

## What the platform layer is

The platform layer is the **host-side control plane** for a distributed PyPTO Serving deployment. It manages:

- Instance lifecycle (start, stop, health)
- Deployment configuration distribution
- Host-side channel creation and teardown
- Module initialization and service scheduling

It is not a model execution layer. Model kernels, tensor movement, KV cache, batching, and token scheduling belong to Model Support and are executed through Simpler on each device. The platform exists so that Model Support does not have to manage MPI ranks, HiCR channels, or distributed lifecycle directly.

## Instance roles

Every MPI rank in a deployment plays one of four roles, determined by its instance ID and the deployment configuration:

| Role | Count | Responsibility |
|---|---|---|
| **Deployer** | 1 (root) | Reads deployment config, broadcasts it to all other ranks, then transitions to a coordinator or replica role |
| **Coordinator** | one per partition | Routes jobs to replicas, accumulates outputs, fires completion callbacks |
| **Replica** | one or more per partition | Executes model computation via Simpler on a dedicated NPU device |
| **Request Manager** | 1 | Entry point for client requests; maps to the partition owning the user-interface edge |

Device assignment for replicas: `deviceId = instanceId - numPartitions`. Each replica rank owns exactly one NPU.

## Module system

All platform behaviour is expressed as `serving::modules::Module` instances owned by the `serving::system::Engine`. The engine drives a fixed lifecycle:

```
initialize() → run() → [service loop] → terminate() → await() → finalize()
```

Modules can register a periodic background service (via `taskr::Service`) that runs inside the service loop between the `run()` and `terminate()` phases. The engine coordinates the lifecycle of all instances over RPC, so every rank progresses through the same phases in lockstep.

### Modules in this PR

| Module | Purpose |
|---|---|
| `broadcastDeployment` | Deployer sends deployment JSON to all workers over RPC at initialize time |
| `channelController` | Desired-vs-actual reconciliation loop; creates and tears down HiCR SPSC channels |
| `service` | Wraps `taskr::Runtime`; owns and drives background `taskr::Service` instances |

### Deferred modules (next PRs)

| Module | Purpose |
|---|---|
| `channelDispatcher` | Polls subscribed input channels; dispatches messages to registered handlers |
| `taskScheduler` | Registers named `taskr::Task` instances; drives taskr through the module lifecycle |
| `roles::coordinator` | Job queue and replica dispatch; completion callback when all outputs are gathered |
| `roles::replica` | Receives coordinator input, invokes `processFc`, returns outputs |
| `heartbeat` | Periodic health check between coordinators and replicas |

## Channel model

All channels in this PR are **host-side HiCR SPSC channels** carrying variable-size payloads and fixed-size metadata. They are used for control traffic and small tensors.

In-device tensor channels — where the hot path (prefill/decode token data) moves directly between NPU devices without staging through host memory — are **not implemented here**. They are the next major milestone and unblock the TP/PP tensor data path.

## What is deliberately out of scope

- Per-token scheduling (hot path)
- KV cache management
- Batching and sampling policy
- Python bindings
- `RuntimePlan` update protocol and watch/subscribe API
- Dynamic scaling, drain, and fault recovery
- Topology-aware placement
74 changes: 74 additions & 0 deletions platform/docs/model-support-interface.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Model Support Interface

This document describes what the Model Support layer needs to know and control when integrating with the platform.

## What the platform tells you

At startup, after `broadcastDeployment` completes, every rank knows:

- **Its role**: coordinator, replica, or request manager — determined by instance ID and the deployment config
- **Its device index** (replicas only): `deviceId = instanceId - numPartitions`
- **The full deployment graph**: partitions, tasks, edges, and which edges carry user-interface traffic

Model Support should use this information to initialize Simpler on replica ranks before the engine starts.

## What you provide

### 1. Compiled Simpler artifacts

Replica ranks need the five runtime library paths in `RuntimeBinaries`. The platform does not locate or validate them. Pass them via command-line arguments or configuration alongside the deployment JSON.

### 2. A `processFc` per task function name

For each task declared in the deployment configuration, Model Support registers a function:

```cpp
std::function<void(serving::modules::roles::TaskContext &context)>
```

This is the only point where Model Support code runs during execution. Everything else — job routing, input aggregation, output forwarding, channel lifecycle — is handled by the platform.

### 3. Request/response edge names

The deployment config declares a `RequestManager` with an input edge name and an output edge name. Model Support must ensure:

- The input edge name matches the edge from which user requests arrive
- The output edge name matches the edge to which results are written
- The task that reads the input edge does not have any other inter-partition inputs
- The task that writes the output edge does not have any other inter-partition outputs

These constraints are verified by `Deployment::verify()` at startup.

## What you do not own

| Concern | Owner |
|---|---|
| MPI rank management | Platform (via HiCR `InstanceManager`) |
| Channel creation and teardown | Platform (`channelController` module) |
| Deployment broadcast | Platform (`broadcastDeployment` module) |
| Job routing from coordinator to replica | Platform (`coordinator::Module`) |
| Input aggregation and output forwarding | Platform (`replica::Module`) |
| Engine lifecycle (init/run/terminate) | Platform (`Engine`) |
| Replica health monitoring | Platform (heartbeat, future) |

## RuntimePlan — future interface

The current PR does not yet expose a `RuntimePlan`. A future milestone will provide a versioned, observable snapshot of what the platform has actually instantiated:

- Which replicas are live, draining, or unavailable
- Which channels exist and what their endpoints are
- Safe `active → draining → removed` state transitions before any channel is deleted

Model Support will consume this object to make routing and scheduling decisions (KV-locality-aware replica selection, drain-aware request assignment, channel handle lookup) without duplicating platform resource state. The same object will be queryable by operators and higher-level control loops for observability.

Until `RuntimePlan` is implemented, Model Support should treat the deployment configuration as the ground truth of what is running.

## Tensor channel hot path — future

All channels are currently host-side. For the prefill/decode token data path, in-device tensor channels will carry tensor payloads directly between NPU devices without staging through host memory. When implemented:

- Replica `processFc` will receive device-memory `LocalMemorySlot` handles rather than host buffers
- `toDevice` / `toHost` staging in the process function becomes unnecessary
- The `processFc` signature does not change

Model Support should design the process function to be forward-compatible: check whether the incoming `LocalMemorySlot` is a host or device buffer and branch accordingly.
52 changes: 52 additions & 0 deletions platform/docs/modules/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Modules

Source paths:

- `include/modules/module.hpp`
- `include/modules/subscription.hpp`
- `include/modules/service/module.hpp`
- `include/modules/channelController/module.hpp`
- `include/modules/broadcastDeployment/module.hpp`

The modules layer defines reusable platform components that can be installed into `serving::system::Engine`. Modules use a common lifecycle and may optionally expose a periodic TaskR service.

## Base Module

`serving::modules::Module` is the abstract base class for all platform modules.

Main responsibilities:

- Provide a uniform lifecycle: `initialize()`, `run()`, `terminate()`, `await()`, and `finalize()`.
- Optionally create a `taskr::Service` when constructed with a positive interval.
- Let the engine discover and register periodic services through `hasService()` and `getService()`.

The base module does not define deployment semantics itself. It only defines how a platform component participates in the runtime lifecycle.

## Lifecycle

The engine calls module methods in this order:

1. `initialize()` before the instance is marked running.
2. `run()` when the deployer starts itself or sends start RPCs to workers.
3. `terminate()` after stop is requested.
4. `await()` after termination starts.
5. `finalize()` after module work has stopped.

Periodic modules implement their work in the protected `service()` method. The base constructor wraps that method in a `taskr::Service` using the requested interval.

## Subscription

`serving::modules::Subscription` binds a message type, an input channel, and a callback:

- Message type: `channels::Message::messageType_t`.
- Edge: `std::shared_ptr<channels::Input>`.
- Handler: `std::function<void(const std::shared_ptr<channels::Input>, const channels::Message &)>`.

This type is a small ownership wrapper for message-driven modules. It does not poll or dispatch by itself.

## Relation To Platform Design

The module interface supports the issue #32 goal that platform management should be a side service, not a model-execution abstraction. Modules can bootstrap deployment state, reconcile channels, or run control services without placing platform calls in the token-level model path.

## Future work
- More modules are coming
51 changes: 51 additions & 0 deletions platform/docs/modules/broadcast-deployment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Broadcast Deployment Module

Source path: `include/modules/broadcastDeployment/module.hpp`

The broadcast deployment module distributes a serialized deployment configuration from a deployer instance to other instances through RPC.

## Main Type

`serving::modules::broadcastDeployment::Module` derives from `serving::modules::Module`.

Constructor inputs:

- `HiCR::InstanceManager`
- `HiCR::ComputeManager`
- `HiCR::frontend::RPCEngine`
- Deployer instance id.
- Current instance id.
- Optional `serving::configuration::Deployment` for the deployer.

The deployer constructor receives the deployment object. Worker instances use the constructor without a deployment and fetch it during initialization.

## RPC Flow

All instances register an RPC target named `__SERVING_REQUEST_DEPLOYMENT_CONFIGURATION_RPC_NAME`.

When a worker initializes:

1. It requests the deployment RPC from the deployer instance.
2. It waits for the return value.
3. It parses the returned JSON string.
4. It constructs a local `configuration::Deployment` from that JSON.
5. It frees the RPC return-value memory slot.

When the deployer initializes:

- It listens for deployment requests from non-deployer instances.
- On request, it serializes `_deployment` to JSON and submits the serialized string as the RPC return value.

## Relation To Platform Design

This module implements the static bootstrap direction from issue #32. It ensures all instances can obtain the same desired deployment state before channels, services, coordinators, and replicas are initialized.

## Example

`examples/modules/broadcastDeployment/broadcastDeployment.cpp` parses a deployment only on the root instance, creates the broadcast module on every instance, initializes the engine, and prints the received deployment on each instance.

## Current Limitations

- Deployment distribution is init-only and does not publish incremental updates.
- There is no versioning or consistency protocol for dynamic reconfiguration.
- The deployer listens in a loop based on the current instance list and assumes startup-time distribution.
63 changes: 63 additions & 0 deletions platform/docs/modules/channel-controller.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Channel Controller Module

Source path: `include/modules/channelController/module.hpp`

The channel controller manages channel creation through a desired-state reconciliation loop. It turns desired producer and consumer registrations into initialized HiCR-backed `Output` and `Input` channels.

## Main Type

`serving::modules::channelController::Module` derives from `serving::modules::Module` and runs as a periodic service by default.

Constructor inputs:

- Current instance id.
- Ordered list of `HiCR::CommunicationManager *` objects.
- Global memory-slot exchange tag.
- Reconciliation interval in milliseconds.

At least one communication manager is required.

## Desired State

The controller keeps separate desired and actual maps for producers and consumers.

`addDesiredProducer(targetId, channelId, edge, keyBuilder)` creates a desired `Output` channel for an edge.

`addDesiredConsumer(sourceId, channelId, edge, keyBuilder)` creates a desired `Input` channel for an edge.

`removeDesiredProducer(edgeName)` and `removeDesiredConsumer(edgeName)` remove desired entries.

`hasProducer(edgeName)` and `hasConsumer(edgeName)` report whether actual channels exist.

`getProducer(edgeName)` and `getConsumer(edgeName)` return initialized actual channels.

## Reconciliation Flow

The private `reconcile()` method:

1. Diffs desired producers/consumers against actual producers/consumers.
2. Collects memory slots required by missing channels.
3. Groups slots by communication manager.
4. Exchanges global memory slots through the configured managers.
5. Fences each manager on the exchange tag.
6. Initializes missing `Output` and `Input` channels.
7. Moves initialized channels into actual maps.
8. Removes stale actual channels when desired entries disappear.

The module calls `reconcile()` during `initialize()` and from its periodic `service()` method.

## Relation To Platform Design

This module implements the channel-management direction from issue #32: model support can request channels by desired intent, while platform code handles lifecycle and resource exchange.

The current implementation works at the HiCR channel level. Future integration can add a higher-level distinction between host control channels and device tensor payload channels while preserving the same desired-state reconciliation model.

## Example

`examples/modules/channelController/channelController.cpp` parses a deployment, assigns edge managers, registers desired local producer/consumer channels, waits until both are ready, runs a telephone-game message exchange, removes desired channels, and terminates.

## Current Limitations

- The TODO in the source notes that every instance currently needs to run this service for MPI-style backends.
- The controller does not yet expose a frontend that hides backend-specific details from callers.
- Placement policy is provided indirectly through edge HiCR manager assignments.
Loading
Loading