AnthropicallyHuggingFaces commited on
Commit
5db0768
·
verified ·
1 Parent(s): a034bea

Add rollout engine

Browse files
90-execution/distributed-inference/model-rollouts/rollout.py ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """rollout.py - async canary/progressive rollout state machine.
2
+
3
+ States: DRAFT -> APPROVED -> CANARY_PULL -> CANARY_VERIFY -> CANARY_SERVING
4
+ -> PROGRESSIVE_ROLLOUT -> COMPLETE (failures -> PAUSED/ROLLBACK)
5
+ Persist steps + audit to PostgreSQL; use Redis for exclusive transition locks.
6
+ """
7
+ from __future__ import annotations
8
+ import enum
9
+ from dataclasses import dataclass, field
10
+
11
+
12
+ class State(str, enum.Enum):
13
+ DRAFT = "DRAFT"
14
+ APPROVED = "APPROVED"
15
+ CANARY_PULL = "CANARY_PULL"
16
+ CANARY_VERIFY = "CANARY_VERIFY"
17
+ CANARY_SERVING = "CANARY_SERVING"
18
+ PROGRESSIVE_ROLLOUT = "PROGRESSIVE_ROLLOUT"
19
+ COMPLETE = "COMPLETE"
20
+ PAUSED = "PAUSED"
21
+ ROLLBACK = "ROLLBACK"
22
+
23
+
24
+ @dataclass
25
+ class Rollout:
26
+ id: str
27
+ model_id: str
28
+ environment: str
29
+ plan: dict
30
+ state: State = State.DRAFT
31
+ history: list[str] = field(default_factory=list)
32
+
33
+ def transition(self, new: State, why: str = ""):
34
+ valid = {
35
+ State.DRAFT: {State.APPROVED},
36
+ State.APPROVED: {State.CANARY_PULL},
37
+ State.CANARY_PULL: {State.CANARY_VERIFY},
38
+ State.CANARY_VERIFY: {State.CANARY_SERVING, State.ROLLBACK},
39
+ State.CANARY_SERVING: {State.PROGRESSIVE_ROLLOUT, State.PAUSED, State.ROLLBACK},
40
+ State.PROGRESSIVE_ROLLOUT: {State.COMPLETE, State.PAUSED, State.ROLLBACK},
41
+ State.PAUSED: {State.PROGRESSIVE_ROLLOUT, State.ROLLBACK},
42
+ }.get(self.state, set())
43
+ if new not in valid:
44
+ raise ValueError(f"invalid transition {self.state} -> {new}")
45
+ self.state = new
46
+ self.history.append(f"{new.value}:{why}")
47
+
48
+ def gates_pass(self, metrics: dict) -> bool:
49
+ g = self.plan.get("quality_gates", {})
50
+ return all([
51
+ metrics.get("smoke_success_rate", 0) >= g.get("smoke_test_success_rate_min", 1.0),
52
+ metrics.get("error_rate", 1) <= g.get("error_rate_max", 0.01),
53
+ metrics.get("p95_ttft_ms", 1e9) <= g.get("p95_first_token_latency_ms_max", 3000),
54
+ metrics.get("p95_itl_ms", 1e9) <= g.get("p95_inter_token_latency_ms_max", 250),
55
+ ])
56
+
57
+
58
+ def approve(r: Rollout, role: str, ticket: str) -> Rollout:
59
+ need = r.plan.get("approval", {}).get("required_role", "model_operator")
60
+ if role != need or not ticket:
61
+ raise PermissionError(f"requires {need} + change ticket")
62
+ r.transition(State.APPROVED, f"approved_by={role} ticket={ticket}")
63
+ return r