UraionLabs commited on
Commit
47c2ff9
·
verified ·
1 Parent(s): 4989d93

Upload scheduler.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. scheduler.py +191 -0
scheduler.py ADDED
@@ -0,0 +1,191 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Hardware-Aware Prefix Scheduler (Algorithm 1 from DSpark paper).
3
+
4
+ Dynamically selects per-request verification lengths to maximize
5
+ expected system throughput: Θ = τ * SPS(B)
6
+
7
+ where τ is expected accepted tokens, SPS(B) is steps-per-second at batch size B.
8
+
9
+ Key properties:
10
+ - Monotonic candidate selection via global survival probability sorting
11
+ - Early stopping to preserve non-anticipating property
12
+ - Pre-profiled throughput curve (SPS table)
13
+ - Fallback to static threshold or fixed length
14
+ """
15
+
16
+ from typing import Optional
17
+
18
+ import torch
19
+
20
+
21
+ class ThroughputProfile:
22
+ """Pre-profiled engine throughput curve SPS(B).
23
+
24
+ Maps batch size B (tokens per forward pass) to steps-per-second.
25
+ Profiled once during engine initialization.
26
+ """
27
+
28
+ def __init__(self, sps_table: Optional[dict[int, float]] = None):
29
+ """Initialize with an SPS lookup table.
30
+
31
+ Args:
32
+ sps_table: dict mapping batch_size -> steps_per_second.
33
+ If None, uses a default synthetic profile.
34
+ """
35
+ if sps_table is not None:
36
+ self.sps = sps_table
37
+ else:
38
+ # Default synthetic SPS curve: realistic-ish for an A100
39
+ # Starts high for small batches, decreases as batch grows
40
+ self.sps = {
41
+ 1: 200.0,
42
+ 2: 180.0,
43
+ 4: 150.0,
44
+ 8: 120.0,
45
+ 16: 90.0,
46
+ 32: 60.0,
47
+ 64: 35.0,
48
+ 128: 20.0,
49
+ 256: 10.0,
50
+ 512: 5.0,
51
+ }
52
+
53
+ def __call__(self, batch_size: int) -> float:
54
+ """Lookup SPS for a given batch size, with interpolation.
55
+
56
+ Args:
57
+ batch_size: Number of tokens to verify.
58
+
59
+ Returns:
60
+ steps_per_second: Estimated throughput.
61
+ """
62
+ batch_sizes = sorted(self.sps.keys())
63
+
64
+ # Exact match
65
+ if batch_size in self.sps:
66
+ return self.sps[batch_size]
67
+
68
+ # Clamp to range
69
+ if batch_size <= batch_sizes[0]:
70
+ return self.sps[batch_sizes[0]]
71
+ if batch_size >= batch_sizes[-1]:
72
+ return self.sps[batch_sizes[-1]]
73
+
74
+ # Linear interpolation
75
+ for i in range(len(batch_sizes) - 1):
76
+ b_low, b_high = batch_sizes[i], batch_sizes[i + 1]
77
+ if b_low <= batch_size <= b_high:
78
+ frac = (batch_size - b_low) / (b_high - b_low)
79
+ return self.sps[b_low] + frac * (self.sps[b_high] - self.sps[b_low])
80
+
81
+ return self.sps[batch_sizes[0]]
82
+
83
+
84
+ def hardware_aware_prefix_scheduler(
85
+ confidence_scores: list[torch.Tensor],
86
+ throughput_profile: ThroughputProfile,
87
+ gamma: int,
88
+ min_accept_prob: float = 1e-6,
89
+ ) -> list[int]:
90
+ """Hardware-Aware Prefix Scheduler (Algorithm 1 from DSpark paper).
91
+
92
+ For each request r with confidence scores c_{r,1..gamma}:
93
+ 1. Compute prefix survival probabilities a_{r,j} = prod_{i<=j} c_{r,i}
94
+ 2. Globally sort all valid prefix extensions by survival probability
95
+ 3. Greedily add tokens, tracking throughput Θ = τ * SPS(B)
96
+ 4. Early stop when throughput stops improving
97
+ 5. Return per-request verification lengths
98
+
99
+ Args:
100
+ confidence_scores: list of [gamma] tensors, one per request
101
+ throughput_profile: SPS(B) curve
102
+ gamma: maximum block size
103
+ min_accept_prob: minimum survival probability for valid candidates
104
+
105
+ Returns:
106
+ verification_lengths: list of ints, selected ℓ_r per request
107
+ """
108
+ R = len(confidence_scores)
109
+
110
+ # Step 1: Compute prefix survival probabilities
111
+ survival_probs = []
112
+ for r in range(R):
113
+ c = confidence_scores[r]
114
+ a = torch.cumprod(c, dim=-1) # cumulative product
115
+ survival_probs.append(a)
116
+
117
+ # Step 2: Construct candidate space E = {(r, j) | a_{r,j} > min_accept_prob}
118
+ candidates = [] # list of (survival_prob, request_idx, position)
119
+ for r in range(R):
120
+ for j in range(gamma):
121
+ prob = survival_probs[r][j].item()
122
+ if prob > min_accept_prob:
123
+ candidates.append((prob, r, j))
124
+
125
+ # Step 3-5: Greedy selection
126
+ if not candidates:
127
+ return [0] * R
128
+
129
+ # Sort descending by survival probability
130
+ candidates.sort(key=lambda x: -x[0])
131
+
132
+ # Initialize states
133
+ lengths = [0] * R # ℓ_r per request
134
+ B = R # Current batch size (each request has 1 anchor token)
135
+ tau = float(R) # Expected accepts (1 per request for anchor)
136
+ best_throughput = tau * throughput_profile(B)
137
+ best_lengths = list(lengths)
138
+
139
+ # Greedy admission
140
+ for prob, r, j in candidates:
141
+ # Check if this candidate extends a contiguous prefix
142
+ # (i.e., length[r] == j, meaning positions 1..j are already scheduled)
143
+ if lengths[r] != j:
144
+ continue
145
+
146
+ # Update: extend request r's verification length to j+1
147
+ lengths[r] = j + 1
148
+ B += 1 # One extra token
149
+ tau += prob
150
+
151
+ # Current throughput
152
+ current_throughput = tau * throughput_profile(B)
153
+
154
+ if current_throughput > best_throughput + 1e-8:
155
+ best_throughput = current_throughput
156
+ best_lengths = list(lengths)
157
+ else:
158
+ # Early stopping: throughput stopped improving
159
+ # Restore state and exit
160
+ lengths[r] = j
161
+ break
162
+
163
+ return best_lengths
164
+
165
+
166
+ class StaticScheduler:
167
+ """Simple fallback schedulers for verification length."""
168
+
169
+ @staticmethod
170
+ def fixed_length(gamma: int, R: int) -> list[int]:
171
+ """Always verify the full block."""
172
+ return [gamma] * R
173
+
174
+ @staticmethod
175
+ def static_threshold(
176
+ confidence_scores: list[torch.Tensor],
177
+ threshold: float = 0.1,
178
+ gamma: int = 7,
179
+ ) -> list[int]:
180
+ """Verify prefix until confidence drops below threshold."""
181
+ lengths = []
182
+ for c in confidence_scores:
183
+ # Count how many consecutive positions exceed threshold
184
+ count = 0
185
+ for k in range(gamma):
186
+ if k < len(c) and c[k].item() >= threshold:
187
+ count += 1
188
+ else:
189
+ break
190
+ lengths.append(count)
191
+ return lengths