Skip to content

Commit 6b14a3d

Browse files
lilyz-aiclaude
andcommitted
proposal: add temporal endpoint type to Launch (MLI-6425)
RFC for natively managing Temporal activity workers via Launch, with phased implementation plan (fixed-replica MVP + KEDA autoscaling). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 9a10495 commit 6b14a3d

1 file changed

Lines changed: 204 additions & 0 deletions

File tree

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
# Proposal: `temporal` Endpoint Type in Launch
2+
3+
**Author:** lily.zhu@scale.com
4+
**Status:** RFC
5+
**Ticket:** MLI-6425
6+
7+
---
8+
9+
## Problem
10+
11+
Teams running multi-step GPU pipelines (e.g. robotics ego hand keypoints: SVO processing → Dyn-HaMR → hand annotations) need durable, retryable orchestration across heterogeneous GPU types. Today they have two options:
12+
13+
1. **Launch async endpoints (Celery)** — Launch manages the pods, but Celery gives no cross-step durability. If the H100 pod running Dyn-HaMR crashes mid-run, the whole pipeline restarts from step 1.
14+
2. **Raw K8s Deployments via std-ml-srv** — bypasses Launch entirely; teams lose unified deployment API, GPU scheduling integration, and Launch dashboards.
15+
16+
Temporal solves the durability problem: if a pod crashes mid-activity, Temporal retries *only that activity* from the last heartbeat. Phases already completed are not re-run.
17+
18+
The gap is that Launch has no native way to create and manage Temporal activity worker pods. Teams either re-implement the same ~80-line Temporal worker boilerplate per service, or deploy outside Launch.
19+
20+
---
21+
22+
## Proposed Solution
23+
24+
Add `temporal` as a fourth endpoint type in `ModelEndpointType`. A `temporal` endpoint is a K8s Deployment whose pods connect to Temporal server and pull activity tasks from a named task queue — instead of polling a Celery queue.
25+
26+
**What Launch manages:**
27+
- Pod lifecycle (create / update / delete)
28+
- GPU scheduling and node selection
29+
- Scaling (fixed replicas in MVP; Temporal-aware autoscaling as follow-up)
30+
- Unified visibility in Launch dashboards
31+
32+
**What Launch does not touch:**
33+
- Task submission (the Temporal workflow dispatches activities directly)
34+
- Result routing (Temporal handles activity results)
35+
- `/v1/async-tasks` API (not applicable for `temporal` endpoints)
36+
37+
---
38+
39+
## API Changes
40+
41+
### `POST /v1/model-endpoints`
42+
43+
New field on `CreateModelEndpointV1Request`:
44+
45+
```python
46+
temporal_task_queue: Optional[str]
47+
# Required when endpoint_type="temporal".
48+
# The Temporal task queue that workers will poll.
49+
# Example: "robotics-hand-keypoints-temporal"
50+
```
51+
52+
Example request:
53+
54+
```json
55+
{
56+
"name": "hand-keypoints-temporal",
57+
"endpoint_type": "temporal",
58+
"temporal_task_queue": "robotics-hand-keypoints-temporal",
59+
"gpus": 1,
60+
"gpu_type": "nvidia-hopper-h100",
61+
"cpus": 20,
62+
"memory": "200Gi",
63+
"storage": "250Gi",
64+
"min_workers": 0,
65+
"max_workers": 10,
66+
"per_worker": 1,
67+
"model_bundle_id": "...",
68+
"labels": {"team": "robotics", "product": "ego"}
69+
}
70+
```
71+
72+
The `model_bundle_id` points to a bundle whose command runs the Temporal activity worker (e.g. `python -m ml_serve.exe.run_service --task-queue robotics-hand-keypoints-temporal`).
73+
74+
---
75+
76+
## Implementation Plan
77+
78+
### Phase 1 — MVP (fixed replicas, ~2 weeks)
79+
80+
**`domain/entities/model_endpoint_entity.py`**
81+
```python
82+
class ModelEndpointType(str, Enum):
83+
ASYNC = "async"
84+
SYNC = "sync"
85+
STREAMING = "streaming"
86+
TEMPORAL = "temporal" # new
87+
```
88+
89+
**`common/dtos/model_endpoints.py`**
90+
- Add `temporal_task_queue: Optional[str]` to `CreateModelEndpointV1Request` and `UpdateModelEndpointV1Request`
91+
- Validation: required when `endpoint_type == "temporal"`
92+
93+
**`domain/use_cases/model_endpoint_use_cases.py`**
94+
- `validate_deployment_resources`: allow `min_workers=0` for `TEMPORAL` (same as `ASYNC`)
95+
- No `concurrent_requests_per_worker` limit (workers process one activity at a time by default)
96+
97+
**`infra/gateways/resources/k8s_resource_types.py`**
98+
- Add `_TemporalDeploymentArguments` TypedDict:
99+
```python
100+
class _TemporalDeploymentArguments(TypedDict):
101+
TEMPORAL_TASK_QUEUE: str
102+
TEMPORAL_SERVER_HOSTNAME: str
103+
TEMPORAL_SERVER_PORT: str
104+
REPLICAS: int # fixed in MVP; driven by max_workers
105+
```
106+
- Add `DeploymentRunnableImageTemporalGpuArguments` and `...CpuArguments` composite TypedDicts
107+
108+
**`infra/gateways/resources/templates/service_template_config_map*.yaml`**
109+
- Add `deployment-runnable-image-temporal-gpu.yaml` and `...-cpu.yaml` templates
110+
- Key differences from async template:
111+
- No `celery-forwarder` sidecar — the user container IS the Temporal worker
112+
- No `celery.scaleml.autoscaler/*` annotations
113+
- `replicas: ${REPLICAS}` (fixed)
114+
- Env vars injected: `TEMPORAL_TASK_QUEUE`, `TEMPORAL_SERVER_HOSTNAME`, `TEMPORAL_SERVER_PORT`, `CONCURRENCY`
115+
- Readiness probe: TCP check on Temporal worker port (or omit — workers have no HTTP endpoint)
116+
117+
**`infra/gateways/resources/k8s_endpoint_resource_delegate.py`**
118+
- `delete_resources`: add `TEMPORAL` branch (reuses sync cleanup — no Celery queue to delete)
119+
- `create_or_update_resources`: route `TEMPORAL` to new template
120+
121+
**`infra/gateways/resources/live_endpoint_resource_gateway.py`**
122+
- `create_or_update_resources`: skip Celery queue creation for `TEMPORAL` (add to `else` branch or make explicit)
123+
- `get_resources`: skip SQS queue depth polling for `TEMPORAL`
124+
125+
### Phase 2 — Temporal-aware autoscaling (follow-up, ~3 weeks)
126+
127+
Scale worker replicas based on Temporal task queue backlog. Options:
128+
129+
1. **KEDA `temporal` trigger** — KEDA has a [Temporal scaler](https://keda.sh/docs/scalers/temporal/) that polls `GetTaskQueueStats`. Lowest implementation cost; reuses existing KEDA infrastructure.
130+
2. **Custom autoscaler** — mirrors the existing Celery autoscaler pattern but polls Temporal's gRPC API.
131+
132+
Recommendation: KEDA Temporal trigger. Annotation format:
133+
```yaml
134+
temporal.keda.sh/task-queue: "${TEMPORAL_TASK_QUEUE}"
135+
temporal.keda.sh/namespace: "default"
136+
temporal.keda.sh/targetQueueSize: "${PER_WORKER}"
137+
```
138+
139+
---
140+
141+
## What Changes in Caller Code (ego example)
142+
143+
Before — each service writes ~80 lines of custom Temporal boilerplate:
144+
```python
145+
# launch_hand_keypoints/temporal_worker.py (custom, per-service)
146+
_predict = load_predict_fn(...)
147+
148+
@activity.defn(name="handKeypointsActivity")
149+
async def hand_keypoints_activity(inp):
150+
heartbeat_task = loop.create_task(_heartbeat_loop()) # manual
151+
...
152+
153+
async def main():
154+
client = await Client.connect(...) # manual
155+
worker = Worker(client, task_queue=..., ...) # manual
156+
await worker.run()
157+
```
158+
159+
After — service implements one method; Launch manages the rest:
160+
```python
161+
# launch_hand_keypoints/service.py
162+
class HandKeypointsService(ModelServiceApi):
163+
def handle(self, req: dict) -> dict:
164+
result = self._predict(HandKeypointsRequest(**req))
165+
return {"hands_npz_url": ..., "track_info_url": ...}
166+
```
167+
168+
```bash
169+
# Deploy via Launch API (same as any other endpoint)
170+
launch create-endpoint \
171+
--name hand-keypoints-temporal \
172+
--endpoint-type temporal \
173+
--temporal-task-queue robotics-hand-keypoints-temporal \
174+
--gpu-type h100 --gpus 1 \
175+
--min-workers 0 --max-workers 10
176+
```
177+
178+
---
179+
180+
## What This Is Not
181+
182+
- **Not a task submission API.** Launch does not expose `/v1/async-tasks` for `temporal` endpoints. The Temporal workflow is the caller; Launch only manages the worker pods.
183+
- **Not a workflow worker.** Launch manages activity workers only. The workflow definition lives in the caller's codebase and runs on a separate workflow worker (or Temporal Cloud).
184+
- **Not a replacement for Celery async endpoints.** `async` endpoints remain the right choice for request/response workloads where the caller submits tasks via Launch's API. `temporal` is for multi-step pipelines where an external orchestrator coordinates the work.
185+
186+
---
187+
188+
## Alternatives Considered
189+
190+
| Option | Verdict |
191+
|--------|---------|
192+
| Raw K8s Deployment (std-ml-srv `deployment_template_TEMPORAL_gpu.yaml`) | Works today; loses Launch management. Good stopgap, not long-term. |
193+
| Temporal orchestrates existing Launch async endpoints | Extra HTTP round-trip per phase; doesn't use Temporal activities properly. |
194+
| Launch batch jobs per phase | `backoffLimit: 0`, cold start per request, no worker pool. Wrong tool. |
195+
| Temporal Cloud | Doesn't change the worker management problem; workers still need to run somewhere. |
196+
197+
---
198+
199+
## Open Questions
200+
201+
1. **Readiness probe**: Temporal workers have no HTTP endpoint. Should Launch skip the readiness probe for `temporal` endpoints, or should worker images expose a `/healthz` on a sidecar port?
202+
2. **Task submission API**: Should Launch eventually expose a way to *start a Temporal workflow* (not just manage workers)? This would be a larger API addition and is out of scope for Phase 1.
203+
3. **Namespace**: Should `temporal_namespace` be a configurable field, or default to `"default"`?
204+
4. **Multi-queue workers**: Some use cases may want one pod to pull from multiple task queues. Out of scope for now; each endpoint maps to one task queue.

0 commit comments

Comments
 (0)