flashruntime 0.3.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.
- flashml_workloads/__init__.py +7 -0
- flashml_workloads/fedavg_driver.py +569 -0
- flashml_workloads/fedavg_weights.py +223 -0
- flashml_workloads/fedavg_worker.py +166 -0
- flashml_workloads/kmeans_driver.py +134 -0
- flashml_workloads/kmeans_shard.py +69 -0
- flashml_workloads/sgd_trainer.py +127 -0
- flashml_workloads/sharded_kmeans.py +323 -0
- flashml_workloads/sklearn_trial.py +89 -0
- flashruntime/__init__.py +125 -0
- flashruntime/artifacts/__init__.py +25 -0
- flashruntime/artifacts/store.py +228 -0
- flashruntime/backends/__init__.py +26 -0
- flashruntime/backends/base.py +63 -0
- flashruntime/backends/kuberay.py +465 -0
- flashruntime/checkpoint/__init__.py +20 -0
- flashruntime/checkpoint/catalog.py +198 -0
- flashruntime/checkpoint/local.py +109 -0
- flashruntime/checkpoint/store.py +86 -0
- flashruntime/integrations/__init__.py +5 -0
- flashruntime/integrations/huggingface.py +59 -0
- flashruntime/integrations/pytorch.py +52 -0
- flashruntime/integrations/sklearn.py +42 -0
- flashruntime/launchers/__init__.py +130 -0
- flashruntime/launchers/local.py +126 -0
- flashruntime/leases/__init__.py +27 -0
- flashruntime/leases/manager.py +365 -0
- flashruntime/leases/sqlite_store.py +169 -0
- flashruntime/leases/store.py +103 -0
- flashruntime/monitor/__init__.py +7 -0
- flashruntime/monitor/sampler.py +232 -0
- flashruntime/planner/__init__.py +56 -0
- flashruntime/planner/candidates.py +597 -0
- flashruntime/planner/catalog.py +129 -0
- flashruntime/planner/comm.py +95 -0
- flashruntime/planner/explain.py +109 -0
- flashruntime/planner/memory.py +166 -0
- flashruntime/planner/resolve.py +120 -0
- flashruntime/planner/selector.py +169 -0
- flashruntime/planner/timecost.py +81 -0
- flashruntime/profiling/__init__.py +113 -0
- flashruntime/protocol/__init__.py +18 -0
- flashruntime/protocol/plan_v1alpha1.py +320 -0
- flashruntime/protocol/v1alpha1.py +465 -0
- flashruntime/providers/__init__.py +138 -0
- flashruntime/py.typed +0 -0
- flashruntime/recipes/__init__.py +135 -0
- flashruntime/recipes/command.py +166 -0
- flashruntime/recovery/__init__.py +21 -0
- flashruntime/recovery/policy.py +170 -0
- flashruntime/recovery/signals.py +135 -0
- flashruntime/recovery/taxonomy.py +91 -0
- flashruntime/scheduler/__init__.py +170 -0
- flashruntime/sdk.py +402 -0
- flashruntime/service/__init__.py +3 -0
- flashruntime/service/app.py +391 -0
- flashruntime/service/auth.py +180 -0
- flashruntime/service/checkpoints.py +90 -0
- flashruntime/service/cli.py +167 -0
- flashruntime/service/dashboard.py +193 -0
- flashruntime/service/ledger.py +101 -0
- flashruntime/service/modea.py +821 -0
- flashruntime/strategies/__init__.py +156 -0
- flashruntime/strategies/command.py +56 -0
- flashruntime/torch/__init__.py +274 -0
- flashruntime/viewer/__init__.py +20 -0
- flashruntime/viewer/_docs/benchmarks.html +771 -0
- flashruntime/viewer/_docs/concepts/architecture.html +302 -0
- flashruntime/viewer/_docs/get-started.html +263 -0
- flashruntime/viewer/_docs/guides/federated-averaging.html +363 -0
- flashruntime/viewer/_docs/guides/huggingface.html +223 -0
- flashruntime/viewer/_docs/guides/jobspec-and-isolation.html +271 -0
- flashruntime/viewer/_docs/guides/pytorch.html +313 -0
- flashruntime/viewer/_docs/guides/sklearn.html +232 -0
- flashruntime/viewer/_docs/index.html +251 -0
- flashruntime/viewer/_docs/reference/cli.html +254 -0
- flashruntime/viewer/_docs/reference/integrations.html +240 -0
- flashruntime/viewer/_docs/reference/sdk.html +341 -0
- flashruntime/viewer/_docs/reference/torch-helper.html +244 -0
- flashruntime/viewer/_docs/search-index.json +1 -0
- flashruntime/viewer/_docs/tutorials/convnet.html +571 -0
- flashruntime/viewer/_docs/tutorials/fault-tolerance.html +375 -0
- flashruntime/viewer/_docs/tutorials/sklearn-sweeps.html +278 -0
- flashruntime/viewer/flowmap.py +307 -0
- flashruntime/viewer/page.py +594 -0
- flashruntime/viewer/server.py +134 -0
- flashruntime/viewer/state.py +250 -0
- flashruntime/workloads/__init__.py +6 -0
- flashruntime/workloads/command.py +127 -0
- flashruntime-0.3.0.dist-info/METADATA +365 -0
- flashruntime-0.3.0.dist-info/RECORD +95 -0
- flashruntime-0.3.0.dist-info/WHEEL +5 -0
- flashruntime-0.3.0.dist-info/entry_points.txt +2 -0
- flashruntime-0.3.0.dist-info/licenses/LICENSE +202 -0
- flashruntime-0.3.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
"""FlashML planning protocol, version v1alpha1.
|
|
2
|
+
|
|
3
|
+
Wire-visible models for the strategy planner: what a user *asks for*
|
|
4
|
+
(`PlanRequest` = workload + resources + objective) and what the planner
|
|
5
|
+
*answers* (`PlanReport` = a selected `StrategyPlan` plus every candidate
|
|
6
|
+
verdict with its arithmetic). These schemas are the contract between the
|
|
7
|
+
SDK/CLI, FlashML Cloud, and — later — the execution layer that compiles a
|
|
8
|
+
`StrategyPlan` into a running job.
|
|
9
|
+
|
|
10
|
+
Design rules (ADR-0003):
|
|
11
|
+
- Backend-neutral: nothing here requires importing torch/ray/transformers to
|
|
12
|
+
parse. Strategy families and launchers are *names*; strategy compilers
|
|
13
|
+
translate them into real configuration at execution time.
|
|
14
|
+
- Every numeric estimate carries a `basis` (`static` | `profiled` | `ledger`)
|
|
15
|
+
so consumers can tell arithmetic from measurement.
|
|
16
|
+
- Explanations — including *rejected* candidates — are part of the contract,
|
|
17
|
+
not decoration.
|
|
18
|
+
- Additive changes only within v1alpha1; breaking changes go to a new module.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
from typing import Annotated, Literal, Union
|
|
24
|
+
|
|
25
|
+
from pydantic import BaseModel, Field
|
|
26
|
+
|
|
27
|
+
PLAN_API_VERSION = "flashml.dev/v1alpha1"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# ---------------------------------------------------------------------------
|
|
31
|
+
# Workloads — what the user wants to run.
|
|
32
|
+
#
|
|
33
|
+
# Classification is structural (what the computation looks like), never
|
|
34
|
+
# label-based: "LoRA" says which parameters train, not which distributed
|
|
35
|
+
# strategy to use. Each workload kind carries exactly the fields the
|
|
36
|
+
# estimators need.
|
|
37
|
+
# ---------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class TransformerFineTune(BaseModel):
|
|
41
|
+
"""Fine-tune a transformer language model (full, LoRA, or QLoRA).
|
|
42
|
+
|
|
43
|
+
`parameters_b` may be omitted for models in the planner's small catalog
|
|
44
|
+
(e.g. "Qwen/Qwen2.5-7B"); otherwise it is required. `hidden_size` /
|
|
45
|
+
`num_layers` improve the activation estimate; when absent the planner
|
|
46
|
+
derives a typical shape from the parameter count and labels the estimate
|
|
47
|
+
accordingly.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
kind: Literal["transformer_finetune"] = "transformer_finetune"
|
|
51
|
+
model: str = Field(description="Model name, e.g. 'Qwen/Qwen2.5-7B'")
|
|
52
|
+
parameters_b: float | None = Field(
|
|
53
|
+
default=None, gt=0, description="Total parameters in billions"
|
|
54
|
+
)
|
|
55
|
+
method: Literal["full", "lora", "qlora"] = "lora"
|
|
56
|
+
lora_rank: int = Field(default=16, ge=1, le=256)
|
|
57
|
+
precision: Literal["bf16", "fp16", "fp32"] = "bf16"
|
|
58
|
+
optimizer: Literal["adamw", "adamw_8bit", "sgd_momentum"] = "adamw"
|
|
59
|
+
seq_len: int = Field(default=2048, ge=64)
|
|
60
|
+
micro_batch_per_gpu: int = Field(default=1, ge=1)
|
|
61
|
+
grad_accum: int = Field(default=8, ge=1)
|
|
62
|
+
activation_checkpointing: bool | None = Field(
|
|
63
|
+
default=None, description="None = planner decides"
|
|
64
|
+
)
|
|
65
|
+
hidden_size: int | None = Field(default=None, ge=64)
|
|
66
|
+
num_layers: int | None = Field(default=None, ge=1)
|
|
67
|
+
train_tokens_m: float | None = Field(
|
|
68
|
+
default=None,
|
|
69
|
+
gt=0,
|
|
70
|
+
description="Training set size in millions of tokens; enables time/cost/deadline estimates",
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class PyTorchTraining(BaseModel):
|
|
75
|
+
"""Generic PyTorch deep-learning training (CNNs, GNNs, custom models).
|
|
76
|
+
|
|
77
|
+
Without a transformer's regular shape the planner cannot derive
|
|
78
|
+
activation memory structurally, so the user supplies (or accepts a
|
|
79
|
+
pessimistic default for) `activation_gb_per_gpu`.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
kind: Literal["pytorch_training"] = "pytorch_training"
|
|
83
|
+
model: str = Field(default="custom", description="Informational model name")
|
|
84
|
+
parameters_m: float = Field(gt=0, description="Total parameters in millions")
|
|
85
|
+
trainable_fraction: float = Field(default=1.0, gt=0, le=1.0)
|
|
86
|
+
precision: Literal["bf16", "fp16", "fp32"] = "fp32"
|
|
87
|
+
optimizer: Literal["adamw", "adamw_8bit", "sgd_momentum"] = "adamw"
|
|
88
|
+
activation_gb_per_gpu: float | None = Field(
|
|
89
|
+
default=None,
|
|
90
|
+
gt=0,
|
|
91
|
+
description="Measured/estimated activation memory per GPU; default is a pessimistic assumption",
|
|
92
|
+
)
|
|
93
|
+
est_train_gpu_hours: float | None = Field(
|
|
94
|
+
default=None, gt=0, description="Single-GPU wall-clock estimate; enables time/cost"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class ClassicalML(BaseModel):
|
|
99
|
+
"""Classical machine learning (scikit-learn / XGBoost style)."""
|
|
100
|
+
|
|
101
|
+
kind: Literal["classical_ml"] = "classical_ml"
|
|
102
|
+
library: Literal["sklearn", "xgboost"] = "sklearn"
|
|
103
|
+
algorithm: str = Field(default="unspecified", description="e.g. 'kmeans', 'random_forest'")
|
|
104
|
+
dataset_mb: float = Field(gt=0, description="In-memory dataset size in MB")
|
|
105
|
+
supports_partial_fit: bool = Field(
|
|
106
|
+
default=False,
|
|
107
|
+
description="True if the estimator can learn incrementally (enables sharded Mode A execution)",
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class IndependentTasks(BaseModel):
|
|
112
|
+
"""A set of tasks with no communication between them (Mode A).
|
|
113
|
+
|
|
114
|
+
Hyperparameter search, cross-validation folds, batch inference shards,
|
|
115
|
+
per-seed synthetic-data generation, evaluation suites.
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
kind: Literal["independent_tasks"] = "independent_tasks"
|
|
119
|
+
task_kind: Literal[
|
|
120
|
+
"hyperparameter_search", "batch_inference", "evaluation", "preprocessing", "other"
|
|
121
|
+
] = "hyperparameter_search"
|
|
122
|
+
task_count: int = Field(ge=1)
|
|
123
|
+
est_minutes_per_task: float = Field(default=10.0, gt=0)
|
|
124
|
+
needs_gpu: bool = False
|
|
125
|
+
vram_gb_per_task: float = Field(default=0.0, ge=0)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
Workload = Annotated[
|
|
129
|
+
Union[TransformerFineTune, PyTorchTraining, ClassicalML, IndependentTasks],
|
|
130
|
+
Field(discriminator="kind"),
|
|
131
|
+
]
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
# ---------------------------------------------------------------------------
|
|
135
|
+
# Resources and objective — what the user has, and what they care about.
|
|
136
|
+
# ---------------------------------------------------------------------------
|
|
137
|
+
|
|
138
|
+
Interconnect = Literal[
|
|
139
|
+
"same_host_nvlink", # NVLink/NVSwitch inside one machine
|
|
140
|
+
"same_host_pcie", # multiple GPUs on one machine over PCIe
|
|
141
|
+
"multi_node_ib", # InfiniBand / RoCE between machines
|
|
142
|
+
"multi_node_100g", # 100 Gb/s Ethernet
|
|
143
|
+
"multi_node_10g", # 10 Gb/s Ethernet
|
|
144
|
+
"wan", # internet-grade links (community devices)
|
|
145
|
+
]
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class Resources(BaseModel):
|
|
149
|
+
"""The compute the plan may use. GPU-count 0 means CPU-only."""
|
|
150
|
+
|
|
151
|
+
gpus: int = Field(default=0, ge=0)
|
|
152
|
+
gpu_type: str | None = Field(
|
|
153
|
+
default=None, description="e.g. 'A100-40GB', 'L40S', 'RTX4090'; keys the GPU catalog"
|
|
154
|
+
)
|
|
155
|
+
vram_gb: float | None = Field(
|
|
156
|
+
default=None, gt=0, description="Per-GPU VRAM; overrides the catalog value"
|
|
157
|
+
)
|
|
158
|
+
hosts: int = Field(default=1, ge=1, description="How many machines the GPUs are spread over")
|
|
159
|
+
interconnect: Interconnect = "same_host_pcie"
|
|
160
|
+
cpu_ram_gb: float = Field(default=32.0, gt=0, description="Host RAM per machine")
|
|
161
|
+
cpu_cores: int = Field(default=8, ge=1)
|
|
162
|
+
hourly_cost_usd_per_gpu: float | None = Field(default=None, ge=0)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class Objective(BaseModel):
|
|
166
|
+
"""Constraints and preference. Hard constraints reject plans; the mode
|
|
167
|
+
orders the survivors."""
|
|
168
|
+
|
|
169
|
+
mode: Literal["cheapest", "fastest", "balanced", "reliable"] = "balanced"
|
|
170
|
+
max_cost_usd: float | None = Field(default=None, gt=0)
|
|
171
|
+
deadline_minutes: float | None = Field(default=None, gt=0)
|
|
172
|
+
allow_quantization: bool = True
|
|
173
|
+
allow_cpu_offload: bool = True
|
|
174
|
+
allow_nvme_offload: bool = False
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
class PlanRequest(BaseModel):
|
|
178
|
+
"""The planner's input: intent + resources + objective."""
|
|
179
|
+
|
|
180
|
+
apiVersion: str = PLAN_API_VERSION
|
|
181
|
+
kind: Literal["PlanRequest"] = "PlanRequest"
|
|
182
|
+
workload: Workload
|
|
183
|
+
resources: Resources = Field(default_factory=Resources)
|
|
184
|
+
objective: Objective = Field(default_factory=Objective)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
# ---------------------------------------------------------------------------
|
|
188
|
+
# Planner output
|
|
189
|
+
# ---------------------------------------------------------------------------
|
|
190
|
+
|
|
191
|
+
EstimateBasis = Literal["static", "profiled", "ledger"]
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class Estimate(BaseModel):
|
|
195
|
+
"""A number that admits where it came from."""
|
|
196
|
+
|
|
197
|
+
value: float
|
|
198
|
+
unit: str
|
|
199
|
+
basis: EstimateBasis = "static"
|
|
200
|
+
note: str = ""
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class MemoryBreakdown(BaseModel):
|
|
204
|
+
"""Per-GPU peak memory estimate in GB (1 GB = 1e9 bytes), by component.
|
|
205
|
+
|
|
206
|
+
`transient_gb` covers FSDP/ZeRO-3 per-layer all-gather peaks;
|
|
207
|
+
`cpu_offload_gb` is host RAM claimed by offloaded state (checked against
|
|
208
|
+
`Resources.cpu_ram_gb` — planners that only check VRAM kill hosts).
|
|
209
|
+
"""
|
|
210
|
+
|
|
211
|
+
weights_gb: float = 0.0
|
|
212
|
+
gradients_gb: float = 0.0
|
|
213
|
+
optimizer_gb: float = 0.0
|
|
214
|
+
activations_gb: float = 0.0
|
|
215
|
+
transient_gb: float = 0.0
|
|
216
|
+
overhead_gb: float = 0.0
|
|
217
|
+
total_gb: float = 0.0
|
|
218
|
+
cpu_offload_gb: float = 0.0
|
|
219
|
+
basis: EstimateBasis = "static"
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
class LibraryRef(BaseModel):
|
|
223
|
+
"""One library the selected strategy is built on, and its role."""
|
|
224
|
+
|
|
225
|
+
name: str
|
|
226
|
+
role: str # e.g. "launcher", "strategy", "workload", "checkpoint", "runtime"
|
|
227
|
+
purpose: str = ""
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
CandidateStatus = Literal[
|
|
231
|
+
"selected", # the winner
|
|
232
|
+
"feasible", # would work; lost the ranking
|
|
233
|
+
"infeasible", # fails a physical check (memory, network)
|
|
234
|
+
"rejected_policy", # violates a user constraint (budget, deadline, allow_* flag)
|
|
235
|
+
"rejected_dominated", # feasible but strictly worse than another candidate
|
|
236
|
+
]
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
class CandidateVerdict(BaseModel):
|
|
240
|
+
"""One evaluated candidate, with its numbers and its fate.
|
|
241
|
+
|
|
242
|
+
Rejections are half the product: a user who sees *why* DDP was infeasible
|
|
243
|
+
trusts the plan that was chosen.
|
|
244
|
+
"""
|
|
245
|
+
|
|
246
|
+
name: str
|
|
247
|
+
strategy_family: str
|
|
248
|
+
workers: int
|
|
249
|
+
status: CandidateStatus
|
|
250
|
+
memory: MemoryBreakdown | None = None
|
|
251
|
+
est_time_min: Estimate | None = None
|
|
252
|
+
est_cost_usd: Estimate | None = None
|
|
253
|
+
scaling_efficiency: float | None = None
|
|
254
|
+
profiling_required: bool = False
|
|
255
|
+
reasons: list[str] = Field(default_factory=list)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
class CheckpointPolicy(BaseModel):
|
|
259
|
+
backend: str = "pytorch_dcp"
|
|
260
|
+
interval_seconds: int = 300
|
|
261
|
+
note: str = ""
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
class StrategyPlan(BaseModel):
|
|
265
|
+
"""The planner's answer: a frozen, backend-neutral execution plan.
|
|
266
|
+
|
|
267
|
+
Frozen means: `plan_id` is a content hash of the request + decisions +
|
|
268
|
+
planner version, recorded on the job attempt at execution time so every
|
|
269
|
+
incident can answer "what did we think, and why".
|
|
270
|
+
"""
|
|
271
|
+
|
|
272
|
+
apiVersion: str = PLAN_API_VERSION
|
|
273
|
+
kind: Literal["StrategyPlan"] = "StrategyPlan"
|
|
274
|
+
plan_id: str
|
|
275
|
+
planner_version: str
|
|
276
|
+
|
|
277
|
+
workload_mode: Literal["local", "independent_tasks", "coordinated_training"]
|
|
278
|
+
strategy_family: str # e.g. "ddp", "fsdp2", "zero3_cpu_offload", "lease_tasks", "local_process"
|
|
279
|
+
launcher: str # e.g. "torchrun", "flashruntime-leases", "local"
|
|
280
|
+
|
|
281
|
+
workers: int = 1
|
|
282
|
+
gpus_per_worker: int = 0
|
|
283
|
+
colocated: bool = True
|
|
284
|
+
|
|
285
|
+
precision: str | None = None
|
|
286
|
+
quantization: str | None = None # e.g. "nf4" for QLoRA frozen weights
|
|
287
|
+
peft: str | None = None # e.g. "lora(r=16)"
|
|
288
|
+
micro_batch_per_gpu: int | None = None
|
|
289
|
+
grad_accum: int | None = None
|
|
290
|
+
activation_checkpointing: bool | None = None
|
|
291
|
+
offload: Literal["none", "optimizer_cpu", "params_cpu", "nvme"] = "none"
|
|
292
|
+
|
|
293
|
+
libraries: list[LibraryRef] = Field(default_factory=list)
|
|
294
|
+
checkpoint: CheckpointPolicy | None = None
|
|
295
|
+
|
|
296
|
+
memory: MemoryBreakdown | None = None
|
|
297
|
+
est_time_min: Estimate | None = None
|
|
298
|
+
est_cost_usd: Estimate | None = None
|
|
299
|
+
scaling_efficiency: float | None = None
|
|
300
|
+
profiling_required: bool = False
|
|
301
|
+
|
|
302
|
+
selected_because: list[str] = Field(default_factory=list)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
class PlanReport(BaseModel):
|
|
306
|
+
"""Everything the planner concluded: the winner and the full field.
|
|
307
|
+
|
|
308
|
+
`selected` is None when no candidate survived; `no_valid_strategy_hint`
|
|
309
|
+
then carries the nearest-miss analysis (the minimal relaxation that would
|
|
310
|
+
unlock a plan) — a dead end must still be a useful answer.
|
|
311
|
+
"""
|
|
312
|
+
|
|
313
|
+
apiVersion: str = PLAN_API_VERSION
|
|
314
|
+
kind: Literal["PlanReport"] = "PlanReport"
|
|
315
|
+
planner_version: str
|
|
316
|
+
request_digest: str
|
|
317
|
+
selected: StrategyPlan | None = None
|
|
318
|
+
candidates: list[CandidateVerdict] = Field(default_factory=list)
|
|
319
|
+
warnings: list[str] = Field(default_factory=list)
|
|
320
|
+
no_valid_strategy_hint: str | None = None
|