flexsds 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- flexsds/__init__.py +4 -0
- flexsds/assets/ptit_logo.png +0 -0
- flexsds/cli/__init__.py +1 -0
- flexsds/cli/banner.py +84 -0
- flexsds/cli/main.py +330 -0
- flexsds/cli/output.py +69 -0
- flexsds/cli/task_runner.py +171 -0
- flexsds/cli/wizard.py +360 -0
- flexsds/client/__init__.py +4 -0
- flexsds/client/chat.py +137 -0
- flexsds/client/client.py +107 -0
- flexsds/client/monitor.py +95 -0
- flexsds/core/__init__.py +44 -0
- flexsds/core/compatibility.py +442 -0
- flexsds/core/controller.py +502 -0
- flexsds/core/cost_model.py +530 -0
- flexsds/core/credentials.py +111 -0
- flexsds/core/hardware.py +77 -0
- flexsds/core/tree_topology.py +296 -0
- flexsds/profiles/profile_sm75_t4.json +62 -0
- flexsds/profiles/profile_sm89_l4.json +194 -0
- flexsds/server/__init__.py +1 -0
- flexsds/server/colab_runner.py +62 -0
- flexsds/server/local_runner.py +208 -0
- flexsds/server/modal_app.py +50 -0
- flexsds/server/modal_runner.py +98 -0
- flexsds/server/vscode_cli.py +82 -0
- flexsds-0.2.0.dist-info/METADATA +204 -0
- flexsds-0.2.0.dist-info/RECORD +62 -0
- flexsds-0.2.0.dist-info/WHEEL +5 -0
- flexsds-0.2.0.dist-info/entry_points.txt +2 -0
- flexsds-0.2.0.dist-info/top_level.txt +4 -0
- pipeline/entity_eval.py +171 -0
- pipeline/examples/node1_extractor_input_m0_04_001.txt +9 -0
- pipeline/examples/node1_extractor_output_m0_04_001.json +90 -0
- pipeline/extractor.py +502 -0
- pipeline/invoice_models.py +163 -0
- pipeline/node2_questions.py +30 -0
- pipeline/openai_compatible.py +87 -0
- pipeline/prompts/README.md +18 -0
- pipeline/prompts/node1_extractor_system.md +92 -0
- pipeline/prompts/node3_report_user.md +5 -0
- pipeline/report_writer.py +143 -0
- pipeline/schemas/invoice_extraction.schema.json +469 -0
- pipeline/sql_guard.py +140 -0
- pipeline/sqlite/schema.sql +219 -0
- pipeline/text_to_sql.py +170 -0
- pipeline/validation.py +166 -0
- pipeline/w1_workload.py +139 -0
- proposer/__init__.py +31 -0
- proposer/adapter.py +153 -0
- proposer/controller.py +460 -0
- proposer/cost_model.py +216 -0
- proposer/tree_adaptive.py +466 -0
- server/__init__.py +3 -0
- server/adaptive_scheduler.py +215 -0
- server/budget_planner.py +287 -0
- server/lineage.py +290 -0
- server/lineage_diagnostics.py +38 -0
- server/lineage_executor.py +129 -0
- server/lineage_startup.py +47 -0
- server/t4_rowwise_attention.py +262 -0
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
"""TreeAdaptiveController v0.2 module with 2D tree topologies and dual-tier safety guards."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import logging
|
|
5
|
+
import math
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from collections import deque
|
|
8
|
+
from typing import Dict, List, Optional, Tuple, Any
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
from flexsds.core.cost_model import FlexCostModel, SolverResult
|
|
12
|
+
from flexsds.core.tree_topology import get_tree_topology, TreeTopology
|
|
13
|
+
except ModuleNotFoundError:
|
|
14
|
+
from src.flexsds.core.cost_model import FlexCostModel, SolverResult
|
|
15
|
+
from src.flexsds.core.tree_topology import get_tree_topology, TreeTopology
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
# Value threshold representing missing/sentinel VRAM readings (e.g. 1e9 from server planners)
|
|
20
|
+
SENTINEL_VRAM_THRESHOLD_MB = 1.0e8
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class BudgetDecision:
|
|
25
|
+
"""Represents a budget selection decision made by BudgetController."""
|
|
26
|
+
k: int
|
|
27
|
+
alpha_ema: float
|
|
28
|
+
expected_tokens: float
|
|
29
|
+
reason: str
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class BudgetController:
|
|
33
|
+
"""Base Adaptive Speculative Controller."""
|
|
34
|
+
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
window_size: int = 10,
|
|
38
|
+
ema_beta: float = 0.8,
|
|
39
|
+
initial_alpha: float = 0.8,
|
|
40
|
+
fallback_threshold: float = 0.65,
|
|
41
|
+
high_threshold: float = 0.80,
|
|
42
|
+
candidate_budgets: Optional[List[int]] = None,
|
|
43
|
+
min_free_vram_mb: float = 1500.0,
|
|
44
|
+
**kwargs,
|
|
45
|
+
):
|
|
46
|
+
self.window_size = window_size
|
|
47
|
+
self.ema_beta = ema_beta
|
|
48
|
+
self.alpha_ema = initial_alpha
|
|
49
|
+
self.initial_alpha = initial_alpha
|
|
50
|
+
self.fallback_threshold = fallback_threshold
|
|
51
|
+
self.high_threshold = high_threshold
|
|
52
|
+
self.candidate_budgets = candidate_budgets or [0, 2, 3, 4]
|
|
53
|
+
self.min_free_vram_mb = min_free_vram_mb
|
|
54
|
+
self._window: deque[float] = deque(maxlen=window_size)
|
|
55
|
+
self._history: List[dict] = []
|
|
56
|
+
|
|
57
|
+
def reset(self, initial_alpha: Optional[float] = None) -> None:
|
|
58
|
+
if initial_alpha is not None:
|
|
59
|
+
self.alpha_ema = initial_alpha
|
|
60
|
+
self.initial_alpha = initial_alpha
|
|
61
|
+
self._window.clear()
|
|
62
|
+
self._history.clear()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
TREE_TOPOLOGIES: Dict[str, Dict[str, Any]] = {
|
|
66
|
+
"T0": {"num_nodes": 0, "k": 0, "depth": 0},
|
|
67
|
+
"T4": {"num_nodes": 4, "k": 2, "depth": 2},
|
|
68
|
+
"T8": {"num_nodes": 8, "k": 3, "depth": 3},
|
|
69
|
+
"T16": {"num_nodes": 16, "k": 4, "depth": 4},
|
|
70
|
+
"T32": {"num_nodes": 32, "k": 5, "depth": 5},
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@dataclass
|
|
75
|
+
class TreeAdaptiveDecision(BudgetDecision):
|
|
76
|
+
"""Represents an adaptive tree budget decision made by TreeAdaptiveController v0.2."""
|
|
77
|
+
topology_name: str = "T0"
|
|
78
|
+
num_nodes: int = 0
|
|
79
|
+
alpha_effective: float = 0.8
|
|
80
|
+
alpha_star: Optional[float] = None
|
|
81
|
+
speedup: float = 1.0
|
|
82
|
+
status: str = "ok" # "ok" | "memory_guard" | "concurrency_throttled" | "fallback_gate" | "probing" | "zero_crash_fallback"
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class TreeAdaptiveController(BudgetController):
|
|
86
|
+
"""Hardware-aware TreeAdaptive Speculative Controller v0.2.
|
|
87
|
+
|
|
88
|
+
Dynamically selects optimal 2D tree topology (T0, T4, T8, T16, T32) against the GPU cost model,
|
|
89
|
+
enforces dual-tier memory guard with hysteresis, throttles concurrency under high load,
|
|
90
|
+
and provides adaptive probing with zero-crash resilience.
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
def __init__(
|
|
94
|
+
self,
|
|
95
|
+
cost_model: Optional[FlexCostModel] = None,
|
|
96
|
+
candidate_topologies: Optional[List[str]] = None,
|
|
97
|
+
min_free_vram_mb: float = 1500.0,
|
|
98
|
+
kv_cache_high_pct: float = 90.0,
|
|
99
|
+
kv_cache_recover_pct: float = 80.0,
|
|
100
|
+
cooldown_steps: int = 3,
|
|
101
|
+
window_size: int = 10,
|
|
102
|
+
ema_beta: float = 0.8,
|
|
103
|
+
initial_alpha: float = 0.8,
|
|
104
|
+
**kwargs,
|
|
105
|
+
):
|
|
106
|
+
super().__init__(
|
|
107
|
+
window_size=window_size,
|
|
108
|
+
ema_beta=ema_beta,
|
|
109
|
+
initial_alpha=initial_alpha,
|
|
110
|
+
min_free_vram_mb=min_free_vram_mb,
|
|
111
|
+
**kwargs,
|
|
112
|
+
)
|
|
113
|
+
self.cost_model = cost_model or FlexCostModel()
|
|
114
|
+
self.candidate_topologies = candidate_topologies or ["T0", "T4", "T8", "T16", "T32"]
|
|
115
|
+
self.kv_cache_high_pct = kv_cache_high_pct
|
|
116
|
+
self.kv_cache_recover_pct = kv_cache_recover_pct
|
|
117
|
+
self.cooldown_steps = cooldown_steps
|
|
118
|
+
|
|
119
|
+
self._step_count: int = 0
|
|
120
|
+
self._observation_count: int = 0
|
|
121
|
+
self._fallback_steps: int = 0
|
|
122
|
+
self._memory_guard_active: bool = False
|
|
123
|
+
self._guard_trigger_source: str = "none"
|
|
124
|
+
self._cooldown_counter: int = 0
|
|
125
|
+
self._guard_reason: str = ""
|
|
126
|
+
self._last_selected_depth: int = 0
|
|
127
|
+
self._last_selected_topology: str = "T0"
|
|
128
|
+
|
|
129
|
+
@property
|
|
130
|
+
def step_count(self) -> int:
|
|
131
|
+
return self._step_count
|
|
132
|
+
|
|
133
|
+
@step_count.setter
|
|
134
|
+
def step_count(self, value: int) -> None:
|
|
135
|
+
self._step_count = value
|
|
136
|
+
self._observation_count = value
|
|
137
|
+
|
|
138
|
+
def reset(self, initial_alpha: Optional[float] = None) -> None:
|
|
139
|
+
"""Resets controller state when beginning a new sequence/prompt."""
|
|
140
|
+
super().reset(initial_alpha=initial_alpha)
|
|
141
|
+
if initial_alpha is None:
|
|
142
|
+
self.alpha_ema = self.initial_alpha
|
|
143
|
+
self._step_count = 0
|
|
144
|
+
self._observation_count = 0
|
|
145
|
+
self._fallback_steps = 0
|
|
146
|
+
self._memory_guard_active = False
|
|
147
|
+
self._guard_trigger_source = "none"
|
|
148
|
+
self._cooldown_counter = 0
|
|
149
|
+
self._guard_reason = ""
|
|
150
|
+
self._last_selected_depth = 0
|
|
151
|
+
self._last_selected_topology = "T0"
|
|
152
|
+
|
|
153
|
+
@staticmethod
|
|
154
|
+
def _is_valid_vram(free_vram_mb: Optional[float]) -> bool:
|
|
155
|
+
"""Validates that VRAM telemetry is present, finite, non-negative, and not a sentinel value."""
|
|
156
|
+
if free_vram_mb is None or math.isnan(free_vram_mb) or math.isinf(free_vram_mb):
|
|
157
|
+
return False
|
|
158
|
+
return 0.0 <= free_vram_mb < SENTINEL_VRAM_THRESHOLD_MB
|
|
159
|
+
|
|
160
|
+
@staticmethod
|
|
161
|
+
def _is_valid_kv(kv_cache_usage_pct: Optional[float]) -> bool:
|
|
162
|
+
"""Validates that KV-cache usage percentage is present, finite, and in [0, 100]."""
|
|
163
|
+
if kv_cache_usage_pct is None or math.isnan(kv_cache_usage_pct) or math.isinf(kv_cache_usage_pct):
|
|
164
|
+
return False
|
|
165
|
+
return 0.0 <= kv_cache_usage_pct <= 100.0
|
|
166
|
+
|
|
167
|
+
def get_effective_alpha(self) -> float:
|
|
168
|
+
"""Returns unbiased alpha taking into account warm-up bias correction and NaN self-healing."""
|
|
169
|
+
if math.isnan(self.alpha_ema) or math.isinf(self.alpha_ema):
|
|
170
|
+
self.alpha_ema = self.initial_alpha
|
|
171
|
+
|
|
172
|
+
if self._observation_count == 0:
|
|
173
|
+
return self.initial_alpha
|
|
174
|
+
|
|
175
|
+
beta_power = self.ema_beta ** self._observation_count
|
|
176
|
+
denominator = 1.0 - beta_power
|
|
177
|
+
if denominator <= 1e-6:
|
|
178
|
+
return self.alpha_ema
|
|
179
|
+
|
|
180
|
+
unbiased = (self.alpha_ema - self.initial_alpha * beta_power) / denominator
|
|
181
|
+
return min(max(unbiased, 0.0), 1.0)
|
|
182
|
+
|
|
183
|
+
def get_stats(self) -> dict:
|
|
184
|
+
"""Exports runtime telemetry matching Interface A v0.2 contract."""
|
|
185
|
+
tree_budgets = [h["k"] for h in self._history]
|
|
186
|
+
topologies = [h.get("topology_name", "T0") for h in self._history]
|
|
187
|
+
return {
|
|
188
|
+
"alpha_ema": self.alpha_ema,
|
|
189
|
+
"effective_alpha": self.get_effective_alpha(),
|
|
190
|
+
"tree_budgets": tree_budgets,
|
|
191
|
+
"topologies": topologies,
|
|
192
|
+
"step_count": self._step_count,
|
|
193
|
+
"observation_count": self._observation_count,
|
|
194
|
+
"mean_k": (sum(tree_budgets) / len(tree_budgets)) if tree_budgets else 0.0,
|
|
195
|
+
"history": list(self._history),
|
|
196
|
+
"memory_guard_active": self._memory_guard_active,
|
|
197
|
+
"fallback_steps": self._fallback_steps,
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
def update(self, num_accepted: int, num_drafted: int) -> float:
|
|
201
|
+
"""Updates rolling alpha telemetry from the latest step verification."""
|
|
202
|
+
self._step_count += 1
|
|
203
|
+
|
|
204
|
+
if num_drafted <= 0 or num_accepted < 0:
|
|
205
|
+
return self.alpha_ema
|
|
206
|
+
|
|
207
|
+
self._observation_count += 1
|
|
208
|
+
d = self._last_selected_depth
|
|
209
|
+
|
|
210
|
+
if d > 0 and num_drafted > d:
|
|
211
|
+
step_alpha = min(max(num_accepted / d, 0.0), 1.0)
|
|
212
|
+
else:
|
|
213
|
+
step_alpha = min(max(num_accepted / num_drafted, 0.0), 1.0)
|
|
214
|
+
|
|
215
|
+
self._window.append(step_alpha)
|
|
216
|
+
self.alpha_ema = self.ema_beta * self.alpha_ema + (1.0 - self.ema_beta) * step_alpha
|
|
217
|
+
return self.alpha_ema
|
|
218
|
+
|
|
219
|
+
def _get_probe_interval(self, batch_size: int) -> int:
|
|
220
|
+
"""Returns load-aware probing interval T_probe(B)."""
|
|
221
|
+
if batch_size <= 4:
|
|
222
|
+
return 15
|
|
223
|
+
elif batch_size <= 16:
|
|
224
|
+
return 20
|
|
225
|
+
else:
|
|
226
|
+
return 30
|
|
227
|
+
|
|
228
|
+
def _get_throttled_candidates(self, batch_size: int) -> List[str]:
|
|
229
|
+
"""Filters candidate topologies according to concurrency throttling tier."""
|
|
230
|
+
if batch_size <= 4:
|
|
231
|
+
allowed = ["T0", "T4", "T8", "T16", "T32"]
|
|
232
|
+
elif batch_size <= 12:
|
|
233
|
+
allowed = ["T0", "T4", "T8", "T16"]
|
|
234
|
+
elif batch_size <= 24:
|
|
235
|
+
allowed = ["T0", "T4", "T8"]
|
|
236
|
+
else:
|
|
237
|
+
allowed = ["T0", "T4"]
|
|
238
|
+
|
|
239
|
+
return [t for t in self.candidate_topologies if t in allowed]
|
|
240
|
+
|
|
241
|
+
def _make_decision(
|
|
242
|
+
self,
|
|
243
|
+
topology_name: str,
|
|
244
|
+
num_nodes: int,
|
|
245
|
+
alpha_effective: float,
|
|
246
|
+
alpha_star: Optional[float],
|
|
247
|
+
speedup: float,
|
|
248
|
+
status: str,
|
|
249
|
+
reason: str,
|
|
250
|
+
expected_tokens: float,
|
|
251
|
+
depth: int,
|
|
252
|
+
) -> TreeAdaptiveDecision:
|
|
253
|
+
self._last_selected_depth = depth
|
|
254
|
+
self._last_selected_topology = topology_name
|
|
255
|
+
decision = TreeAdaptiveDecision(
|
|
256
|
+
k=num_nodes,
|
|
257
|
+
alpha_ema=self.alpha_ema,
|
|
258
|
+
expected_tokens=expected_tokens,
|
|
259
|
+
reason=reason,
|
|
260
|
+
topology_name=topology_name,
|
|
261
|
+
num_nodes=num_nodes,
|
|
262
|
+
alpha_effective=alpha_effective,
|
|
263
|
+
alpha_star=alpha_star,
|
|
264
|
+
speedup=speedup,
|
|
265
|
+
status=status,
|
|
266
|
+
)
|
|
267
|
+
self._history.append({
|
|
268
|
+
"k": num_nodes,
|
|
269
|
+
"topology_name": topology_name,
|
|
270
|
+
"alpha_effective": alpha_effective,
|
|
271
|
+
"alpha_star": alpha_star,
|
|
272
|
+
"speedup": speedup,
|
|
273
|
+
"status": status,
|
|
274
|
+
"reason": reason,
|
|
275
|
+
})
|
|
276
|
+
return decision
|
|
277
|
+
|
|
278
|
+
def select_budget(
|
|
279
|
+
self,
|
|
280
|
+
batch_size: int = 1,
|
|
281
|
+
free_vram_mb: float = 16000.0,
|
|
282
|
+
kv_cache_usage_pct: float = 0.0,
|
|
283
|
+
) -> TreeAdaptiveDecision:
|
|
284
|
+
"""Selects optimal tree topology considering memory, load, and dynamic break-even speedup."""
|
|
285
|
+
try:
|
|
286
|
+
# TẦNG 1: DUAL-TIER MEMORY GUARD
|
|
287
|
+
vram_valid = self._is_valid_vram(free_vram_mb)
|
|
288
|
+
kv_valid = self._is_valid_kv(kv_cache_usage_pct)
|
|
289
|
+
|
|
290
|
+
if not kv_valid:
|
|
291
|
+
is_vram_starved = False
|
|
292
|
+
is_kv_pressured = True
|
|
293
|
+
effective_kv_high = self.kv_cache_high_pct
|
|
294
|
+
effective_kv_recover = self.kv_cache_recover_pct
|
|
295
|
+
trigger_source = "kv"
|
|
296
|
+
else:
|
|
297
|
+
if vram_valid:
|
|
298
|
+
is_vram_starved = free_vram_mb < self.min_free_vram_mb
|
|
299
|
+
effective_kv_high = self.kv_cache_high_pct
|
|
300
|
+
effective_kv_recover = self.kv_cache_recover_pct
|
|
301
|
+
else:
|
|
302
|
+
is_vram_starved = False
|
|
303
|
+
effective_kv_high = 75.0
|
|
304
|
+
effective_kv_recover = 65.0
|
|
305
|
+
|
|
306
|
+
is_kv_pressured = kv_cache_usage_pct > effective_kv_high
|
|
307
|
+
if is_vram_starved and is_kv_pressured:
|
|
308
|
+
trigger_source = "both"
|
|
309
|
+
elif is_vram_starved:
|
|
310
|
+
trigger_source = "vram"
|
|
311
|
+
elif is_kv_pressured:
|
|
312
|
+
trigger_source = "kv"
|
|
313
|
+
else:
|
|
314
|
+
trigger_source = "none"
|
|
315
|
+
|
|
316
|
+
if is_vram_starved or is_kv_pressured:
|
|
317
|
+
self._memory_guard_active = True
|
|
318
|
+
self._guard_trigger_source = trigger_source
|
|
319
|
+
self._cooldown_counter = self.cooldown_steps
|
|
320
|
+
self._fallback_steps = 0
|
|
321
|
+
reason_parts = []
|
|
322
|
+
if is_vram_starved:
|
|
323
|
+
reason_parts.append(f"free VRAM: {free_vram_mb:.0f}MB < {self.min_free_vram_mb:.0f}MB")
|
|
324
|
+
if is_kv_pressured:
|
|
325
|
+
if not kv_valid:
|
|
326
|
+
reason_parts.append(f"invalid KV telemetry: {kv_cache_usage_pct}")
|
|
327
|
+
elif not vram_valid:
|
|
328
|
+
reason_parts.append(f"KV cache: {kv_cache_usage_pct:.1f}% > tight threshold {effective_kv_high:.1f}% (missing VRAM)")
|
|
329
|
+
else:
|
|
330
|
+
reason_parts.append(f"KV cache: {kv_cache_usage_pct:.1f}% > {effective_kv_high:.1f}%")
|
|
331
|
+
self._guard_reason = " | ".join(reason_parts)
|
|
332
|
+
|
|
333
|
+
return self._make_decision(
|
|
334
|
+
topology_name="T0",
|
|
335
|
+
num_nodes=0,
|
|
336
|
+
alpha_effective=self.get_effective_alpha(),
|
|
337
|
+
alpha_star=None,
|
|
338
|
+
speedup=1.0,
|
|
339
|
+
status="memory_guard",
|
|
340
|
+
reason=f"Memory guard active ({self._guard_reason})",
|
|
341
|
+
expected_tokens=1.0,
|
|
342
|
+
depth=0,
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
if self._memory_guard_active:
|
|
346
|
+
if self._guard_trigger_source in ["vram", "both"]:
|
|
347
|
+
can_recover = (
|
|
348
|
+
vram_valid
|
|
349
|
+
and (free_vram_mb >= self.min_free_vram_mb)
|
|
350
|
+
and kv_valid
|
|
351
|
+
and (kv_cache_usage_pct <= effective_kv_recover)
|
|
352
|
+
)
|
|
353
|
+
else:
|
|
354
|
+
can_recover = kv_valid and (kv_cache_usage_pct <= effective_kv_recover)
|
|
355
|
+
|
|
356
|
+
if self._cooldown_counter > 0:
|
|
357
|
+
self._cooldown_counter -= 1
|
|
358
|
+
self._fallback_steps = 0
|
|
359
|
+
return self._make_decision(
|
|
360
|
+
topology_name="T0",
|
|
361
|
+
num_nodes=0,
|
|
362
|
+
alpha_effective=self.get_effective_alpha(),
|
|
363
|
+
alpha_star=None,
|
|
364
|
+
speedup=1.0,
|
|
365
|
+
status="memory_guard",
|
|
366
|
+
reason=f"Memory guard cooldown ({self._cooldown_counter + 1} steps remaining; {self._guard_reason})",
|
|
367
|
+
expected_tokens=1.0,
|
|
368
|
+
depth=0,
|
|
369
|
+
)
|
|
370
|
+
else:
|
|
371
|
+
if can_recover:
|
|
372
|
+
self._memory_guard_active = False
|
|
373
|
+
self._guard_trigger_source = "none"
|
|
374
|
+
else:
|
|
375
|
+
self._fallback_steps = 0
|
|
376
|
+
return self._make_decision(
|
|
377
|
+
topology_name="T0",
|
|
378
|
+
num_nodes=0,
|
|
379
|
+
alpha_effective=self.get_effective_alpha(),
|
|
380
|
+
alpha_star=None,
|
|
381
|
+
speedup=1.0,
|
|
382
|
+
status="memory_guard",
|
|
383
|
+
reason=f"Memory guard holding (conditions not met: VRAM={free_vram_mb}MB valid={vram_valid}, KV={kv_cache_usage_pct}%)",
|
|
384
|
+
expected_tokens=1.0,
|
|
385
|
+
depth=0,
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
# TẦNG 2: UNBIASED ALPHA
|
|
389
|
+
alpha_effective = self.get_effective_alpha()
|
|
390
|
+
t4_result = self.cost_model.solve_alpha_star("T4", batch_size)
|
|
391
|
+
ref_alpha_star_t4 = t4_result.alpha_star if t4_result.is_feasible else None
|
|
392
|
+
|
|
393
|
+
# TẦNG 3: CONCURRENCY THROTTLING
|
|
394
|
+
allowed_candidates = self._get_throttled_candidates(batch_size)
|
|
395
|
+
|
|
396
|
+
if batch_size > 24 and alpha_effective < 0.85:
|
|
397
|
+
self._fallback_steps += 1
|
|
398
|
+
probe_interval = self._get_probe_interval(batch_size)
|
|
399
|
+
if self._fallback_steps >= probe_interval:
|
|
400
|
+
self._fallback_steps = 0
|
|
401
|
+
t4_topo = get_tree_topology("T4")
|
|
402
|
+
exp_tok = t4_topo.lookup_expected_acceptance(alpha_effective)
|
|
403
|
+
return self._make_decision(
|
|
404
|
+
topology_name="T4",
|
|
405
|
+
num_nodes=4,
|
|
406
|
+
alpha_effective=alpha_effective,
|
|
407
|
+
alpha_star=ref_alpha_star_t4,
|
|
408
|
+
speedup=1.0,
|
|
409
|
+
status="probing",
|
|
410
|
+
reason=f"Adaptive probing (interval {probe_interval} steps at B={batch_size})",
|
|
411
|
+
expected_tokens=exp_tok,
|
|
412
|
+
depth=4,
|
|
413
|
+
)
|
|
414
|
+
else:
|
|
415
|
+
return self._make_decision(
|
|
416
|
+
topology_name="T0",
|
|
417
|
+
num_nodes=0,
|
|
418
|
+
alpha_effective=alpha_effective,
|
|
419
|
+
alpha_star=ref_alpha_star_t4,
|
|
420
|
+
speedup=1.0,
|
|
421
|
+
status="concurrency_throttled",
|
|
422
|
+
reason=f"Concurrency throttling at B={batch_size} > 24 with alpha={alpha_effective:.3f} < 0.85",
|
|
423
|
+
expected_tokens=1.0,
|
|
424
|
+
depth=0,
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
# TẦNG 4: DYNAMIC FALLBACK GATE & SPEEDUP MAXIMIZATION
|
|
428
|
+
best_topo, best_speedup = self.cost_model.select_optimal_topology(
|
|
429
|
+
alpha=alpha_effective,
|
|
430
|
+
batch_size=batch_size,
|
|
431
|
+
candidate_topologies=allowed_candidates,
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
if best_topo == "T0" or best_speedup <= 1.0:
|
|
435
|
+
self._fallback_steps += 1
|
|
436
|
+
probe_interval = self._get_probe_interval(batch_size)
|
|
437
|
+
if self._fallback_steps >= probe_interval:
|
|
438
|
+
self._fallback_steps = 0
|
|
439
|
+
t4_topo = get_tree_topology("T4")
|
|
440
|
+
exp_tok = t4_topo.lookup_expected_acceptance(alpha_effective)
|
|
441
|
+
return self._make_decision(
|
|
442
|
+
topology_name="T4",
|
|
443
|
+
num_nodes=4,
|
|
444
|
+
alpha_effective=alpha_effective,
|
|
445
|
+
alpha_star=ref_alpha_star_t4,
|
|
446
|
+
speedup=1.0,
|
|
447
|
+
status="probing",
|
|
448
|
+
reason=f"Adaptive probing (interval {probe_interval} steps at B={batch_size})",
|
|
449
|
+
expected_tokens=exp_tok,
|
|
450
|
+
depth=4,
|
|
451
|
+
)
|
|
452
|
+
else:
|
|
453
|
+
return self._make_decision(
|
|
454
|
+
topology_name="T0",
|
|
455
|
+
num_nodes=0,
|
|
456
|
+
alpha_effective=alpha_effective,
|
|
457
|
+
alpha_star=ref_alpha_star_t4,
|
|
458
|
+
speedup=1.0,
|
|
459
|
+
status="fallback_gate",
|
|
460
|
+
reason=f"Dynamic fallback gate (max speedup {best_speedup:.3f} <= 1.0, step {self._fallback_steps}/{probe_interval})",
|
|
461
|
+
expected_tokens=1.0,
|
|
462
|
+
depth=0,
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
self._fallback_steps = 0
|
|
466
|
+
topo_obj = get_tree_topology(best_topo)
|
|
467
|
+
num_nodes = topo_obj.num_tokens
|
|
468
|
+
depth = topo_obj.depth
|
|
469
|
+
exp_tok = topo_obj.lookup_expected_acceptance(alpha_effective)
|
|
470
|
+
solver_res = self.cost_model.solve_alpha_star(best_topo, batch_size)
|
|
471
|
+
best_alpha_star = solver_res.alpha_star if solver_res.is_feasible else None
|
|
472
|
+
|
|
473
|
+
return self._make_decision(
|
|
474
|
+
topology_name=best_topo,
|
|
475
|
+
num_nodes=num_nodes,
|
|
476
|
+
alpha_effective=alpha_effective,
|
|
477
|
+
alpha_star=best_alpha_star,
|
|
478
|
+
speedup=best_speedup,
|
|
479
|
+
status="ok",
|
|
480
|
+
reason=f"Optimal topology {best_topo} (Speedup={best_speedup:.3f}x, alpha*={best_alpha_star})",
|
|
481
|
+
expected_tokens=exp_tok,
|
|
482
|
+
depth=depth,
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
except Exception as exc:
|
|
486
|
+
logger.warning("TreeAdaptiveController zero-crash triggered: %s", exc, exc_info=True)
|
|
487
|
+
return self._make_decision(
|
|
488
|
+
topology_name="T0",
|
|
489
|
+
num_nodes=0,
|
|
490
|
+
alpha_effective=self.initial_alpha,
|
|
491
|
+
alpha_star=None,
|
|
492
|
+
speedup=1.0,
|
|
493
|
+
status="zero_crash_fallback",
|
|
494
|
+
reason=f"Zero-crash fallback due to exception: {exc}",
|
|
495
|
+
expected_tokens=1.0,
|
|
496
|
+
depth=0,
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
# Alias HardwareAdaptiveController to reflect hardware-centric adaptive serving
|
|
501
|
+
HardwareAdaptiveController = TreeAdaptiveController
|
|
502
|
+
HardwareAdaptiveDecision = TreeAdaptiveDecision
|