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,232 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>scikit-learn guide — flashruntime docs</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root { color-scheme: dark; }
|
|
9
|
+
* { box-sizing: border-box; margin: 0; }
|
|
10
|
+
body { font: 14px/1.65 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; background: #0d1117; color: #c9d1d9; }
|
|
11
|
+
a { color: oklch(0.80 0.16 200); text-decoration: none; }
|
|
12
|
+
a:hover { text-decoration: underline; }
|
|
13
|
+
.layout { display: flex; align-items: flex-start; min-height: 100vh; }
|
|
14
|
+
|
|
15
|
+
/* sidebar — PyTorch-docs-like: sections from _nav.yml, current page active */
|
|
16
|
+
.sidebar { flex: 0 0 264px; position: sticky; top: 0; height: 100vh; overflow-y: auto;
|
|
17
|
+
border-right: 1px solid #21262d; background: #0d1117; padding: 20px 16px; }
|
|
18
|
+
.brand { color: #e6edf3; font-size: 15px; letter-spacing: .04em; }
|
|
19
|
+
.brand small { display: block; margin-top: 2px; color: #8b949e; font-size: 10px;
|
|
20
|
+
text-transform: uppercase; letter-spacing: .14em; }
|
|
21
|
+
.search { position: relative; margin: 16px 0; }
|
|
22
|
+
#q { width: 100%; background: #010409; border: 1px solid #21262d; border-radius: 6px;
|
|
23
|
+
color: #c9d1d9; font: 12px ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; padding: 7px 9px; }
|
|
24
|
+
#q:focus { outline: 1px solid oklch(0.80 0.16 200); }
|
|
25
|
+
#results { position: absolute; left: 0; right: 0; top: 112%; z-index: 5; display: none;
|
|
26
|
+
background: #161b22; border: 1px solid #21262d; border-radius: 6px; overflow: hidden; }
|
|
27
|
+
#results.open { display: block; }
|
|
28
|
+
#results a { display: block; padding: 7px 9px; border-bottom: 1px solid #21262d; color: #c9d1d9; }
|
|
29
|
+
#results a:last-child { border-bottom: 0; }
|
|
30
|
+
#results a:hover { background: #010409; text-decoration: none; }
|
|
31
|
+
#results a b { color: #e6edf3; font-weight: 600; }
|
|
32
|
+
#results a span { display: block; margin-top: 2px; color: #8b949e; font-size: 11px; }
|
|
33
|
+
#results .nohit { padding: 7px 9px; color: #8b949e; }
|
|
34
|
+
.nav-section { margin: 16px 0 6px; color: #8b949e; font-size: 10px; font-weight: 600;
|
|
35
|
+
text-transform: uppercase; letter-spacing: .14em; }
|
|
36
|
+
.nav-link { display: block; padding: 4px 8px; border-radius: 5px; color: #c9d1d9; font-size: 13px; }
|
|
37
|
+
.nav-link:hover { background: #161b22; text-decoration: none; }
|
|
38
|
+
.nav-link.active { background: #161b22; color: #e6edf3; box-shadow: inset 2px 0 0 oklch(0.80 0.16 200); }
|
|
39
|
+
|
|
40
|
+
/* content */
|
|
41
|
+
.content { flex: 1 1 auto; max-width: 840px; min-width: 0; padding: 34px 40px 80px; }
|
|
42
|
+
.content h1 { color: #e6edf3; font-size: 26px; margin: 0 0 16px; }
|
|
43
|
+
.content h2 { color: #e6edf3; font-size: 18px; margin: 30px 0 10px;
|
|
44
|
+
padding-top: 10px; border-top: 1px solid #21262d; }
|
|
45
|
+
.content h3 { color: #e6edf3; font-size: 15px; margin: 22px 0 8px; }
|
|
46
|
+
.content p, .content li { color: #c9d1d9; }
|
|
47
|
+
.content ul, .content ol { padding-left: 22px; margin: 10px 0; }
|
|
48
|
+
.content li { margin: 4px 0; }
|
|
49
|
+
.content blockquote { margin: 14px 0; padding: 2px 14px; border-left: 3px solid oklch(0.80 0.18 60);
|
|
50
|
+
background: #161b22; border-radius: 0 6px 6px 0; color: #8b949e; }
|
|
51
|
+
.content :not(pre) > code { background: #010409; border: 1px solid #21262d;
|
|
52
|
+
border-radius: 4px; padding: 1px 5px; font-size: 12.5px; }
|
|
53
|
+
.content table { border-collapse: collapse; margin: 14px 0; display: block; overflow-x: auto; }
|
|
54
|
+
.content th, .content td { border: 1px solid #21262d; padding: 6px 10px; text-align: left; }
|
|
55
|
+
.content th { color: #e6edf3; background: #161b22; }
|
|
56
|
+
|
|
57
|
+
/* code blocks + copy button */
|
|
58
|
+
.codeblock { position: relative; margin: 14px 0; }
|
|
59
|
+
.codeblock pre { background: #010409; border: 1px solid #21262d; border-radius: 8px;
|
|
60
|
+
padding: 14px 16px; overflow-x: auto; }
|
|
61
|
+
.codeblock pre code { padding: 0; border: 0; background: none; font-size: 12.5px; color: #c9d1d9; }
|
|
62
|
+
.copybtn { position: absolute; top: 8px; right: 8px; padding: 3px 8px; cursor: pointer;
|
|
63
|
+
background: #161b22; border: 1px solid #21262d; border-radius: 5px;
|
|
64
|
+
color: #8b949e; font: 11px ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
|
|
65
|
+
.copybtn:hover { color: #e6edf3; border-color: oklch(0.80 0.16 200); }
|
|
66
|
+
|
|
67
|
+
@media (max-width: 800px) {
|
|
68
|
+
.layout { flex-direction: column; }
|
|
69
|
+
.sidebar { position: static; height: auto; width: 100%; flex-basis: auto;
|
|
70
|
+
border-right: 0; border-bottom: 1px solid #21262d; }
|
|
71
|
+
.content { padding: 24px 18px 60px; }
|
|
72
|
+
}
|
|
73
|
+
</style>
|
|
74
|
+
</head>
|
|
75
|
+
<body>
|
|
76
|
+
<div class="layout">
|
|
77
|
+
<aside class="sidebar">
|
|
78
|
+
<a class="brand" href="../index.html">flashruntime<small>documentation</small></a>
|
|
79
|
+
<div class="search">
|
|
80
|
+
<input id="q" type="search" placeholder="Search docs (press /)" autocomplete="off" spellcheck="false">
|
|
81
|
+
<div id="results"></div>
|
|
82
|
+
</div>
|
|
83
|
+
<nav><div class="nav-section">Getting started</div>
|
|
84
|
+
<a class="nav-link" href="../index.html">FlashRuntime</a>
|
|
85
|
+
<a class="nav-link" href="../get-started.html">Get started</a>
|
|
86
|
+
<div class="nav-section">Tutorials</div>
|
|
87
|
+
<a class="nav-link" href="../tutorials/convnet.html">Tutorial: make a ConvNet fault-tolerant</a>
|
|
88
|
+
<a class="nav-link" href="../tutorials/sklearn-sweeps.html">Tutorial: parallel scikit-learn sweeps</a>
|
|
89
|
+
<a class="nav-link" href="../tutorials/fault-tolerance.html">Tutorial: automatic recovery, explained</a>
|
|
90
|
+
<div class="nav-section">Guides</div>
|
|
91
|
+
<a class="nav-link" href="pytorch.html">PyTorch guide</a>
|
|
92
|
+
<a class="nav-link active" href="sklearn.html">scikit-learn guide</a>
|
|
93
|
+
<a class="nav-link" href="huggingface.html">Hugging Face guide</a>
|
|
94
|
+
<a class="nav-link" href="jobspec-and-isolation.html">JobSpec & isolation guide</a>
|
|
95
|
+
<a class="nav-link" href="federated-averaging.html">Federated averaging</a>
|
|
96
|
+
<div class="nav-section">Concepts</div>
|
|
97
|
+
<a class="nav-link" href="../concepts/architecture.html">Architecture</a>
|
|
98
|
+
<div class="nav-section">Reference</div>
|
|
99
|
+
<a class="nav-link" href="../reference/sdk.html">Reference: SDK (`flashruntime`)</a>
|
|
100
|
+
<a class="nav-link" href="../reference/integrations.html">Reference: integrations (`flashruntime.integrations`)</a>
|
|
101
|
+
<a class="nav-link" href="../reference/torch-helper.html">Reference: torch helper (`flashruntime.torch`)</a>
|
|
102
|
+
<a class="nav-link" href="../reference/cli.html">Reference: CLI (`flashruntime`)</a>
|
|
103
|
+
<div class="nav-section">Benchmarks</div>
|
|
104
|
+
<a class="nav-link" href="../benchmarks.html">Benchmarks</a></nav>
|
|
105
|
+
</aside>
|
|
106
|
+
<main class="content"><h1 id="scikit-learn-guide">scikit-learn guide</h1>
|
|
107
|
+
<p>FlashRuntime <strong>operates</strong> your scikit-learn job — it never rewrites your
|
|
108
|
+
estimator. You keep the model and the scoring; FlashRuntime fans a grid out
|
|
109
|
+
into independent tasks, runs them, collects each <code>metrics.json</code>, and ranks the
|
|
110
|
+
results.</p>
|
|
111
|
+
<p>The rule that shapes this whole adapter: sklearn work is <em>embarrassingly
|
|
112
|
+
parallel across runs</em>, <strong>never inside a single <code>.fit()</code></strong>. FlashRuntime fans a
|
|
113
|
+
grid into one independent task per trial — it never tries to split one <code>.fit()</code>
|
|
114
|
+
call, which would change the math.</p>
|
|
115
|
+
<p>For a worked walkthrough, do the
|
|
116
|
+
<a href="../tutorials/sklearn-sweeps.html">sklearn sweeps tutorial</a>.</p>
|
|
117
|
+
<hr />
|
|
118
|
+
<h2 id="the-contract-flags-in-metricsjson-out">The contract: flags in, <code>metrics.json</code> out</h2>
|
|
119
|
+
<p>Your script needs <strong>zero FlashRuntime imports</strong>. It reads hyperparameters from
|
|
120
|
+
CLI flags and writes a flat <code>metrics.json</code> to its working directory. That is
|
|
121
|
+
the entire contract — the same one every framework uses.
|
|
122
|
+
<code>examples/user_sklearn/train.py</code> is plain sklearn end to end.</p>
|
|
123
|
+
<hr />
|
|
124
|
+
<h2 id="fan-a-grid-out">Fan a grid out</h2>
|
|
125
|
+
<p>The <code>integrations.sklearn</code> adapter builds the workload from that script:</p>
|
|
126
|
+
<div class="codeblock"><button class="copybtn" type="button" aria-label="Copy code to clipboard">Copy</button><pre><code class="language-python">import flashruntime as flash
|
|
127
|
+
from flashruntime.integrations import sklearn as fr_sklearn
|
|
128
|
+
|
|
129
|
+
run = flash.submit(fr_sklearn.hpo(
|
|
130
|
+
"train.py",
|
|
131
|
+
{"model": ["logreg", "rf"], "C": [0.1, 1.0], "n_estimators": [50]},
|
|
132
|
+
source="examples/user_sklearn",
|
|
133
|
+
))
|
|
134
|
+
print(f"state={run.state.value} trials={len(run.trials)}")
|
|
135
|
+
print("best:", run.best_trial()) # ranks by outputs.primary_metric
|
|
136
|
+
</code></pre></div>
|
|
137
|
+
<ul>
|
|
138
|
+
<li><strong><code>hpo(script, grid, **kwargs)</code></strong> expands a Cartesian grid
|
|
139
|
+
(<code>{"model": ["logreg", "rf"], "C": [0.1, 1]}</code> → 4 trials) and delegates to
|
|
140
|
+
<code>sweep</code>.</li>
|
|
141
|
+
<li><strong><code>sweep(script, task_params, *, source=".", metric="accuracy_mean",
|
|
142
|
+
maximize=True, python="python")</code></strong> takes an explicit list of param dicts —
|
|
143
|
+
use it when you want a hand-picked, non-Cartesian set.</li>
|
|
144
|
+
</ul>
|
|
145
|
+
<p>Each <code>{placeholder}</code> in the built command is filled from the trial's params, so
|
|
146
|
+
<code>train.py</code> receives <code>--model rf --C 1.0</code> and friends. Because <code>sweep</code> sets
|
|
147
|
+
<code>outputs.primary_metric=metric</code>, <code>run.best_trial()</code> needs no arguments — it
|
|
148
|
+
returns the trial with the highest <code>accuracy_mean</code> (or lowest, when
|
|
149
|
+
<code>maximize=False</code>).</p>
|
|
150
|
+
<hr />
|
|
151
|
+
<h2 id="why-the-fan-out-is-correct-by-construction">Why the fan-out is correct by construction</h2>
|
|
152
|
+
<ul>
|
|
153
|
+
<li><strong>Sequential and isolated.</strong> <code>flash.submit()</code> runs one trial at a time and
|
|
154
|
+
copies each trial's <code>metrics.json</code> out <strong>before</strong> the next trial can
|
|
155
|
+
overwrite it.</li>
|
|
156
|
+
<li><strong>Independent trees.</strong> Each trial gets its own job-scoped checkpoint tree, so
|
|
157
|
+
trials never cross-contaminate.</li>
|
|
158
|
+
<li><strong>Add fault tolerance the usual way.</strong> <code>flash.submit(..., max_restarts=1)</code>
|
|
159
|
+
retries a transient trial failure and fails fast on a deterministic one (a
|
|
160
|
+
bad flag combination that raises the same error every time). See the
|
|
161
|
+
<a href="../tutorials/fault-tolerance.html">fault-tolerance tutorial</a>.</li>
|
|
162
|
+
</ul>
|
|
163
|
+
<hr />
|
|
164
|
+
<h2 id="adding-another-framework">Adding another framework</h2>
|
|
165
|
+
<p>The sklearn adapter is a ~40-line function that builds a <code>CommandWorkload</code> with
|
|
166
|
+
<code>task_params</code> set for fan-out. A new framework adapter follows the same
|
|
167
|
+
pattern: a small function under <code>flashruntime/integrations/</code> that returns a
|
|
168
|
+
<code>CommandWorkload</code> describing <em>what to run</em>, then reuses the same
|
|
169
|
+
launch/collect/rank machinery. The
|
|
170
|
+
<a href="pytorch.html#adding-another-framework">PyTorch adapter</a> is the coordinated-run
|
|
171
|
+
counterpart, and <a href="huggingface.html">Hugging Face</a> is a thin wrapper over it — no
|
|
172
|
+
core change is needed to teach FlashRuntime a new framework.</p></main>
|
|
173
|
+
</div>
|
|
174
|
+
|
|
175
|
+
<script>
|
|
176
|
+
// ---- client-side search: fetch the builder's index, filter as you type -----
|
|
177
|
+
// (<=60 lines, vanilla JS, no external anything — the index is a sibling file.)
|
|
178
|
+
let INDEX = [];
|
|
179
|
+
// Path from THIS page back to the site root. The index stores root-relative
|
|
180
|
+
// URLs (e.g. "tutorials/deep.html"); we fetch it and build result links
|
|
181
|
+
// against ROOT so a page in a subdir resolves them too (see build_docs.py).
|
|
182
|
+
const ROOT = "../";
|
|
183
|
+
const q = document.getElementById("q");
|
|
184
|
+
const results = document.getElementById("results");
|
|
185
|
+
const esc = (s) => String(s).replace(/[&<>"]/g, (c) =>
|
|
186
|
+
({ "&": "&", "<": "<", ">": ">", '"': """ }[c]));
|
|
187
|
+
fetch(ROOT + "search-index.json").then((r) => r.json()).then((d) => { INDEX = d; }).catch(() => {});
|
|
188
|
+
|
|
189
|
+
// A short context window around the first match, so a hit shows WHY it matched.
|
|
190
|
+
function snippet(text, needle) {
|
|
191
|
+
const i = text.toLowerCase().indexOf(needle);
|
|
192
|
+
if (i < 0) return "";
|
|
193
|
+
const start = Math.max(0, i - 32);
|
|
194
|
+
return (start > 0 ? "…" : "") + text.slice(start, i + needle.length + 44).trim() + "…";
|
|
195
|
+
}
|
|
196
|
+
function runSearch() {
|
|
197
|
+
const needle = q.value.trim().toLowerCase();
|
|
198
|
+
if (!needle) { results.className = ""; results.innerHTML = ""; return; }
|
|
199
|
+
const hits = INDEX.map((p) => {
|
|
200
|
+
const inTitle = p.title.toLowerCase().includes(needle);
|
|
201
|
+
const inText = p.text.toLowerCase().includes(needle);
|
|
202
|
+
if (!inTitle && !inText) return null;
|
|
203
|
+
return { url: p.url, title: p.title, snip: inText ? snippet(p.text, needle) : "" };
|
|
204
|
+
}).filter(Boolean).slice(0, 20);
|
|
205
|
+
results.className = "open";
|
|
206
|
+
results.innerHTML = hits.length
|
|
207
|
+
? hits.map((h) => '<a href="' + ROOT + h.url + '"><b>' + esc(h.title) + "</b>" +
|
|
208
|
+
(h.snip ? "<span>" + esc(h.snip) + "</span>" : "") + "</a>").join("")
|
|
209
|
+
: '<div class="nohit">no matches</div>';
|
|
210
|
+
}
|
|
211
|
+
q.addEventListener("input", runSearch);
|
|
212
|
+
q.addEventListener("focus", runSearch);
|
|
213
|
+
document.addEventListener("click", (e) => {
|
|
214
|
+
if (!e.target.closest(".search")) { results.className = ""; } // dismiss on outside click
|
|
215
|
+
});
|
|
216
|
+
document.addEventListener("keydown", (e) => { // "/" focuses search, like PyTorch docs
|
|
217
|
+
if (e.key === "/" && document.activeElement !== q) { e.preventDefault(); q.focus(); }
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// ---- copy buttons: read the <pre>'s own text into the clipboard ------------
|
|
221
|
+
document.querySelectorAll(".copybtn").forEach((btn) => {
|
|
222
|
+
btn.addEventListener("click", () => {
|
|
223
|
+
const code = btn.parentElement.querySelector("pre").innerText;
|
|
224
|
+
navigator.clipboard.writeText(code).then(() => {
|
|
225
|
+
const was = btn.textContent; btn.textContent = "Copied";
|
|
226
|
+
setTimeout(() => { btn.textContent = was; }, 1200);
|
|
227
|
+
}).catch(() => {});
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
</script>
|
|
231
|
+
</body>
|
|
232
|
+
</html>
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>FlashRuntime — flashruntime docs</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root { color-scheme: dark; }
|
|
9
|
+
* { box-sizing: border-box; margin: 0; }
|
|
10
|
+
body { font: 14px/1.65 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; background: #0d1117; color: #c9d1d9; }
|
|
11
|
+
a { color: oklch(0.80 0.16 200); text-decoration: none; }
|
|
12
|
+
a:hover { text-decoration: underline; }
|
|
13
|
+
.layout { display: flex; align-items: flex-start; min-height: 100vh; }
|
|
14
|
+
|
|
15
|
+
/* sidebar — PyTorch-docs-like: sections from _nav.yml, current page active */
|
|
16
|
+
.sidebar { flex: 0 0 264px; position: sticky; top: 0; height: 100vh; overflow-y: auto;
|
|
17
|
+
border-right: 1px solid #21262d; background: #0d1117; padding: 20px 16px; }
|
|
18
|
+
.brand { color: #e6edf3; font-size: 15px; letter-spacing: .04em; }
|
|
19
|
+
.brand small { display: block; margin-top: 2px; color: #8b949e; font-size: 10px;
|
|
20
|
+
text-transform: uppercase; letter-spacing: .14em; }
|
|
21
|
+
.search { position: relative; margin: 16px 0; }
|
|
22
|
+
#q { width: 100%; background: #010409; border: 1px solid #21262d; border-radius: 6px;
|
|
23
|
+
color: #c9d1d9; font: 12px ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; padding: 7px 9px; }
|
|
24
|
+
#q:focus { outline: 1px solid oklch(0.80 0.16 200); }
|
|
25
|
+
#results { position: absolute; left: 0; right: 0; top: 112%; z-index: 5; display: none;
|
|
26
|
+
background: #161b22; border: 1px solid #21262d; border-radius: 6px; overflow: hidden; }
|
|
27
|
+
#results.open { display: block; }
|
|
28
|
+
#results a { display: block; padding: 7px 9px; border-bottom: 1px solid #21262d; color: #c9d1d9; }
|
|
29
|
+
#results a:last-child { border-bottom: 0; }
|
|
30
|
+
#results a:hover { background: #010409; text-decoration: none; }
|
|
31
|
+
#results a b { color: #e6edf3; font-weight: 600; }
|
|
32
|
+
#results a span { display: block; margin-top: 2px; color: #8b949e; font-size: 11px; }
|
|
33
|
+
#results .nohit { padding: 7px 9px; color: #8b949e; }
|
|
34
|
+
.nav-section { margin: 16px 0 6px; color: #8b949e; font-size: 10px; font-weight: 600;
|
|
35
|
+
text-transform: uppercase; letter-spacing: .14em; }
|
|
36
|
+
.nav-link { display: block; padding: 4px 8px; border-radius: 5px; color: #c9d1d9; font-size: 13px; }
|
|
37
|
+
.nav-link:hover { background: #161b22; text-decoration: none; }
|
|
38
|
+
.nav-link.active { background: #161b22; color: #e6edf3; box-shadow: inset 2px 0 0 oklch(0.80 0.16 200); }
|
|
39
|
+
|
|
40
|
+
/* content */
|
|
41
|
+
.content { flex: 1 1 auto; max-width: 840px; min-width: 0; padding: 34px 40px 80px; }
|
|
42
|
+
.content h1 { color: #e6edf3; font-size: 26px; margin: 0 0 16px; }
|
|
43
|
+
.content h2 { color: #e6edf3; font-size: 18px; margin: 30px 0 10px;
|
|
44
|
+
padding-top: 10px; border-top: 1px solid #21262d; }
|
|
45
|
+
.content h3 { color: #e6edf3; font-size: 15px; margin: 22px 0 8px; }
|
|
46
|
+
.content p, .content li { color: #c9d1d9; }
|
|
47
|
+
.content ul, .content ol { padding-left: 22px; margin: 10px 0; }
|
|
48
|
+
.content li { margin: 4px 0; }
|
|
49
|
+
.content blockquote { margin: 14px 0; padding: 2px 14px; border-left: 3px solid oklch(0.80 0.18 60);
|
|
50
|
+
background: #161b22; border-radius: 0 6px 6px 0; color: #8b949e; }
|
|
51
|
+
.content :not(pre) > code { background: #010409; border: 1px solid #21262d;
|
|
52
|
+
border-radius: 4px; padding: 1px 5px; font-size: 12.5px; }
|
|
53
|
+
.content table { border-collapse: collapse; margin: 14px 0; display: block; overflow-x: auto; }
|
|
54
|
+
.content th, .content td { border: 1px solid #21262d; padding: 6px 10px; text-align: left; }
|
|
55
|
+
.content th { color: #e6edf3; background: #161b22; }
|
|
56
|
+
|
|
57
|
+
/* code blocks + copy button */
|
|
58
|
+
.codeblock { position: relative; margin: 14px 0; }
|
|
59
|
+
.codeblock pre { background: #010409; border: 1px solid #21262d; border-radius: 8px;
|
|
60
|
+
padding: 14px 16px; overflow-x: auto; }
|
|
61
|
+
.codeblock pre code { padding: 0; border: 0; background: none; font-size: 12.5px; color: #c9d1d9; }
|
|
62
|
+
.copybtn { position: absolute; top: 8px; right: 8px; padding: 3px 8px; cursor: pointer;
|
|
63
|
+
background: #161b22; border: 1px solid #21262d; border-radius: 5px;
|
|
64
|
+
color: #8b949e; font: 11px ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
|
|
65
|
+
.copybtn:hover { color: #e6edf3; border-color: oklch(0.80 0.16 200); }
|
|
66
|
+
|
|
67
|
+
@media (max-width: 800px) {
|
|
68
|
+
.layout { flex-direction: column; }
|
|
69
|
+
.sidebar { position: static; height: auto; width: 100%; flex-basis: auto;
|
|
70
|
+
border-right: 0; border-bottom: 1px solid #21262d; }
|
|
71
|
+
.content { padding: 24px 18px 60px; }
|
|
72
|
+
}
|
|
73
|
+
</style>
|
|
74
|
+
</head>
|
|
75
|
+
<body>
|
|
76
|
+
<div class="layout">
|
|
77
|
+
<aside class="sidebar">
|
|
78
|
+
<a class="brand" href="index.html">flashruntime<small>documentation</small></a>
|
|
79
|
+
<div class="search">
|
|
80
|
+
<input id="q" type="search" placeholder="Search docs (press /)" autocomplete="off" spellcheck="false">
|
|
81
|
+
<div id="results"></div>
|
|
82
|
+
</div>
|
|
83
|
+
<nav><div class="nav-section">Getting started</div>
|
|
84
|
+
<a class="nav-link active" href="index.html">FlashRuntime</a>
|
|
85
|
+
<a class="nav-link" href="get-started.html">Get started</a>
|
|
86
|
+
<div class="nav-section">Tutorials</div>
|
|
87
|
+
<a class="nav-link" href="tutorials/convnet.html">Tutorial: make a ConvNet fault-tolerant</a>
|
|
88
|
+
<a class="nav-link" href="tutorials/sklearn-sweeps.html">Tutorial: parallel scikit-learn sweeps</a>
|
|
89
|
+
<a class="nav-link" href="tutorials/fault-tolerance.html">Tutorial: automatic recovery, explained</a>
|
|
90
|
+
<div class="nav-section">Guides</div>
|
|
91
|
+
<a class="nav-link" href="guides/pytorch.html">PyTorch guide</a>
|
|
92
|
+
<a class="nav-link" href="guides/sklearn.html">scikit-learn guide</a>
|
|
93
|
+
<a class="nav-link" href="guides/huggingface.html">Hugging Face guide</a>
|
|
94
|
+
<a class="nav-link" href="guides/jobspec-and-isolation.html">JobSpec & isolation guide</a>
|
|
95
|
+
<a class="nav-link" href="guides/federated-averaging.html">Federated averaging</a>
|
|
96
|
+
<div class="nav-section">Concepts</div>
|
|
97
|
+
<a class="nav-link" href="concepts/architecture.html">Architecture</a>
|
|
98
|
+
<div class="nav-section">Reference</div>
|
|
99
|
+
<a class="nav-link" href="reference/sdk.html">Reference: SDK (`flashruntime`)</a>
|
|
100
|
+
<a class="nav-link" href="reference/integrations.html">Reference: integrations (`flashruntime.integrations`)</a>
|
|
101
|
+
<a class="nav-link" href="reference/torch-helper.html">Reference: torch helper (`flashruntime.torch`)</a>
|
|
102
|
+
<a class="nav-link" href="reference/cli.html">Reference: CLI (`flashruntime`)</a>
|
|
103
|
+
<div class="nav-section">Benchmarks</div>
|
|
104
|
+
<a class="nav-link" href="benchmarks.html">Benchmarks</a></nav>
|
|
105
|
+
</aside>
|
|
106
|
+
<main class="content"><h1 id="flashruntime">FlashRuntime</h1>
|
|
107
|
+
<p><strong>FlashRuntime operates your training job — it never rewrites it.</strong> You keep
|
|
108
|
+
the model, the training loop, the loss, the data, and the framework you
|
|
109
|
+
already have. FlashRuntime wraps the reliability and reproducibility layer
|
|
110
|
+
around them: it launches your command, injects the environment it promised,
|
|
111
|
+
tracks your metrics, validates your checkpoints, retries on failure, and
|
|
112
|
+
collects your artifacts.</p>
|
|
113
|
+
<table>
|
|
114
|
+
<thead>
|
|
115
|
+
<tr>
|
|
116
|
+
<th>You own</th>
|
|
117
|
+
<th>FlashRuntime operates</th>
|
|
118
|
+
</tr>
|
|
119
|
+
</thead>
|
|
120
|
+
<tbody>
|
|
121
|
+
<tr>
|
|
122
|
+
<td>model, training loop, loss, data, framework</td>
|
|
123
|
+
<td>launch, environment, metric tracking, checkpoint validity, recovery, artifact collection</td>
|
|
124
|
+
</tr>
|
|
125
|
+
</tbody>
|
|
126
|
+
</table>
|
|
127
|
+
<p>The contract at the boundary is deliberately thin: <strong>arguments in,
|
|
128
|
+
<code>metrics.json</code> out.</strong> A script that already reads its hyperparameters from
|
|
129
|
+
<code>argparse</code> and writes a small JSON file of results needs <em>zero</em> FlashRuntime
|
|
130
|
+
imports to be operated. This is <a href="https://github.com/Zolli-Labs/flashruntime/blob/main/docs/adr/0003-reliability-runtime-first-planner-second.md">ADR-0003</a>'s
|
|
131
|
+
fourth axis in practice: recipes integrate user code; the distributed math is
|
|
132
|
+
always done by your framework (PyTorch DDP, <code>torchrun</code>, Hugging Face, sklearn).</p>
|
|
133
|
+
<h2 id="the-60-second-demo">The 60-second demo</h2>
|
|
134
|
+
<p>Install it, point it at a script, and watch it run — recovering across crashes
|
|
135
|
+
on the way:</p>
|
|
136
|
+
<div class="codeblock"><button class="copybtn" type="button" aria-label="Copy code to clipboard">Copy</button><pre><code class="language-python">import flashruntime as flash
|
|
137
|
+
|
|
138
|
+
run = flash.submit(
|
|
139
|
+
flash.CommandWorkload(
|
|
140
|
+
command="python train.py --epochs 5",
|
|
141
|
+
source=flash.Source(path="~/my-project"),
|
|
142
|
+
outputs=flash.OutputSpec(collect=["metrics.json"]),
|
|
143
|
+
),
|
|
144
|
+
max_restarts=2, # a crashed attempt is classified, then relaunched from
|
|
145
|
+
# the last VALID checkpoint — up to twice
|
|
146
|
+
watch=True, # opens the live run page and prints its URL
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
print(run.state.value) # "SUCCEEDED" (or "FAILED")
|
|
150
|
+
print(run.artifacts) # [PosixPath('.../metrics.json'), ...]
|
|
151
|
+
print(run.viewer_url) # http://127.0.0.1:<port> — the live run page
|
|
152
|
+
</code></pre></div>
|
|
153
|
+
<p><code>flash.submit()</code> compiles that description into a launch spec, runs it as a
|
|
154
|
+
real subprocess, waits, and hands back a <code>Run</code>. <code>command</code> is <code>shlex</code>-split
|
|
155
|
+
(there is no shell — for a pipe, pass <code>command="bash -c '...'"</code>), and <code>source</code>
|
|
156
|
+
is a <code>flash.Source</code>, so <code>~</code> is expanded for you.</p>
|
|
157
|
+
<ul>
|
|
158
|
+
<li><strong><code>max_restarts</code></strong> is the automatic fault-tolerance budget. On a FAILED
|
|
159
|
+
attempt FlashRuntime turns the exit into failure signals, classifies them,
|
|
160
|
+
and consults a <em>versioned, deterministic</em> recovery policy: a deterministic
|
|
161
|
+
application bug fails fast (a retry only re-hits it); anything else relaunches
|
|
162
|
+
the same spec from the job-scoped checkpoint, up to the budget. Same failure</li>
|
|
163
|
+
<li>same policy version ⇒ same action, every time — no LLM in the loop.</li>
|
|
164
|
+
<li><strong><code>watch=True</code></strong> opens the live run page in a browser (and records its URL on
|
|
165
|
+
<code>run.viewer_url</code>). It draws the run's topology, its loss curve, its verified
|
|
166
|
+
checkpoints, and every recovery decision, polling a loopback server with
|
|
167
|
+
<strong>zero external assets</strong>. <code>watch</code> defaults to auto: on at an interactive
|
|
168
|
+
terminal, off in a pipe or CI. These docs are served by the same local
|
|
169
|
+
server at <code>/docs</code>.</li>
|
|
170
|
+
</ul>
|
|
171
|
+
<h2 id="what-flashruntime-does-around-your-job">What FlashRuntime does around your job</h2>
|
|
172
|
+
<ul>
|
|
173
|
+
<li><strong>Launch</strong> — starts your command as a subprocess (locally today; leased to a
|
|
174
|
+
remote node through the coordinator when you compile it to a JobSpec).</li>
|
|
175
|
+
<li><strong>Environment</strong> — injects the env vars a run promises, so the same command is
|
|
176
|
+
reproducible across machines.</li>
|
|
177
|
+
<li><strong>Tracking</strong> — reads the <code>metrics.json</code> your script writes and records it as a
|
|
178
|
+
trial; a fan-out sweep merges each trial's parameters.</li>
|
|
179
|
+
<li><strong>Checkpoints</strong> — the parts-first / manifest-last contract means a
|
|
180
|
+
half-written checkpoint can never look valid; recovery restores only a
|
|
181
|
+
verified, topology-compatible manifest.</li>
|
|
182
|
+
<li><strong>Recovery</strong> — typed, deterministic, logged. Every retry emits a
|
|
183
|
+
<code>FAILURE_CLASSIFIED</code> and a <code>RECOVERY_ACTION_SELECTED</code> event carrying the
|
|
184
|
+
failure class and the policy's human-readable reason.</li>
|
|
185
|
+
<li><strong>Artifacts</strong> — copies your <code>outputs.collect</code> globs out of the run before the
|
|
186
|
+
next trial can overwrite them.</li>
|
|
187
|
+
</ul>
|
|
188
|
+
<h2 id="next">Next</h2>
|
|
189
|
+
<p>Head to <strong><a href="get-started.html">Get started</a></strong> to install FlashRuntime, run your
|
|
190
|
+
first job, and launch your first 2-process DDP run on CPU — no cluster, no GPU
|
|
191
|
+
required.</p></main>
|
|
192
|
+
</div>
|
|
193
|
+
|
|
194
|
+
<script>
|
|
195
|
+
// ---- client-side search: fetch the builder's index, filter as you type -----
|
|
196
|
+
// (<=60 lines, vanilla JS, no external anything — the index is a sibling file.)
|
|
197
|
+
let INDEX = [];
|
|
198
|
+
// Path from THIS page back to the site root. The index stores root-relative
|
|
199
|
+
// URLs (e.g. "tutorials/deep.html"); we fetch it and build result links
|
|
200
|
+
// against ROOT so a page in a subdir resolves them too (see build_docs.py).
|
|
201
|
+
const ROOT = "";
|
|
202
|
+
const q = document.getElementById("q");
|
|
203
|
+
const results = document.getElementById("results");
|
|
204
|
+
const esc = (s) => String(s).replace(/[&<>"]/g, (c) =>
|
|
205
|
+
({ "&": "&", "<": "<", ">": ">", '"': """ }[c]));
|
|
206
|
+
fetch(ROOT + "search-index.json").then((r) => r.json()).then((d) => { INDEX = d; }).catch(() => {});
|
|
207
|
+
|
|
208
|
+
// A short context window around the first match, so a hit shows WHY it matched.
|
|
209
|
+
function snippet(text, needle) {
|
|
210
|
+
const i = text.toLowerCase().indexOf(needle);
|
|
211
|
+
if (i < 0) return "";
|
|
212
|
+
const start = Math.max(0, i - 32);
|
|
213
|
+
return (start > 0 ? "…" : "") + text.slice(start, i + needle.length + 44).trim() + "…";
|
|
214
|
+
}
|
|
215
|
+
function runSearch() {
|
|
216
|
+
const needle = q.value.trim().toLowerCase();
|
|
217
|
+
if (!needle) { results.className = ""; results.innerHTML = ""; return; }
|
|
218
|
+
const hits = INDEX.map((p) => {
|
|
219
|
+
const inTitle = p.title.toLowerCase().includes(needle);
|
|
220
|
+
const inText = p.text.toLowerCase().includes(needle);
|
|
221
|
+
if (!inTitle && !inText) return null;
|
|
222
|
+
return { url: p.url, title: p.title, snip: inText ? snippet(p.text, needle) : "" };
|
|
223
|
+
}).filter(Boolean).slice(0, 20);
|
|
224
|
+
results.className = "open";
|
|
225
|
+
results.innerHTML = hits.length
|
|
226
|
+
? hits.map((h) => '<a href="' + ROOT + h.url + '"><b>' + esc(h.title) + "</b>" +
|
|
227
|
+
(h.snip ? "<span>" + esc(h.snip) + "</span>" : "") + "</a>").join("")
|
|
228
|
+
: '<div class="nohit">no matches</div>';
|
|
229
|
+
}
|
|
230
|
+
q.addEventListener("input", runSearch);
|
|
231
|
+
q.addEventListener("focus", runSearch);
|
|
232
|
+
document.addEventListener("click", (e) => {
|
|
233
|
+
if (!e.target.closest(".search")) { results.className = ""; } // dismiss on outside click
|
|
234
|
+
});
|
|
235
|
+
document.addEventListener("keydown", (e) => { // "/" focuses search, like PyTorch docs
|
|
236
|
+
if (e.key === "/" && document.activeElement !== q) { e.preventDefault(); q.focus(); }
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// ---- copy buttons: read the <pre>'s own text into the clipboard ------------
|
|
240
|
+
document.querySelectorAll(".copybtn").forEach((btn) => {
|
|
241
|
+
btn.addEventListener("click", () => {
|
|
242
|
+
const code = btn.parentElement.querySelector("pre").innerText;
|
|
243
|
+
navigator.clipboard.writeText(code).then(() => {
|
|
244
|
+
const was = btn.textContent; btn.textContent = "Copied";
|
|
245
|
+
setTimeout(() => { btn.textContent = was; }, 1200);
|
|
246
|
+
}).catch(() => {});
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
</script>
|
|
250
|
+
</body>
|
|
251
|
+
</html>
|