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.
Files changed (95) hide show
  1. flashml_workloads/__init__.py +7 -0
  2. flashml_workloads/fedavg_driver.py +569 -0
  3. flashml_workloads/fedavg_weights.py +223 -0
  4. flashml_workloads/fedavg_worker.py +166 -0
  5. flashml_workloads/kmeans_driver.py +134 -0
  6. flashml_workloads/kmeans_shard.py +69 -0
  7. flashml_workloads/sgd_trainer.py +127 -0
  8. flashml_workloads/sharded_kmeans.py +323 -0
  9. flashml_workloads/sklearn_trial.py +89 -0
  10. flashruntime/__init__.py +125 -0
  11. flashruntime/artifacts/__init__.py +25 -0
  12. flashruntime/artifacts/store.py +228 -0
  13. flashruntime/backends/__init__.py +26 -0
  14. flashruntime/backends/base.py +63 -0
  15. flashruntime/backends/kuberay.py +465 -0
  16. flashruntime/checkpoint/__init__.py +20 -0
  17. flashruntime/checkpoint/catalog.py +198 -0
  18. flashruntime/checkpoint/local.py +109 -0
  19. flashruntime/checkpoint/store.py +86 -0
  20. flashruntime/integrations/__init__.py +5 -0
  21. flashruntime/integrations/huggingface.py +59 -0
  22. flashruntime/integrations/pytorch.py +52 -0
  23. flashruntime/integrations/sklearn.py +42 -0
  24. flashruntime/launchers/__init__.py +130 -0
  25. flashruntime/launchers/local.py +126 -0
  26. flashruntime/leases/__init__.py +27 -0
  27. flashruntime/leases/manager.py +365 -0
  28. flashruntime/leases/sqlite_store.py +169 -0
  29. flashruntime/leases/store.py +103 -0
  30. flashruntime/monitor/__init__.py +7 -0
  31. flashruntime/monitor/sampler.py +232 -0
  32. flashruntime/planner/__init__.py +56 -0
  33. flashruntime/planner/candidates.py +597 -0
  34. flashruntime/planner/catalog.py +129 -0
  35. flashruntime/planner/comm.py +95 -0
  36. flashruntime/planner/explain.py +109 -0
  37. flashruntime/planner/memory.py +166 -0
  38. flashruntime/planner/resolve.py +120 -0
  39. flashruntime/planner/selector.py +169 -0
  40. flashruntime/planner/timecost.py +81 -0
  41. flashruntime/profiling/__init__.py +113 -0
  42. flashruntime/protocol/__init__.py +18 -0
  43. flashruntime/protocol/plan_v1alpha1.py +320 -0
  44. flashruntime/protocol/v1alpha1.py +465 -0
  45. flashruntime/providers/__init__.py +138 -0
  46. flashruntime/py.typed +0 -0
  47. flashruntime/recipes/__init__.py +135 -0
  48. flashruntime/recipes/command.py +166 -0
  49. flashruntime/recovery/__init__.py +21 -0
  50. flashruntime/recovery/policy.py +170 -0
  51. flashruntime/recovery/signals.py +135 -0
  52. flashruntime/recovery/taxonomy.py +91 -0
  53. flashruntime/scheduler/__init__.py +170 -0
  54. flashruntime/sdk.py +402 -0
  55. flashruntime/service/__init__.py +3 -0
  56. flashruntime/service/app.py +391 -0
  57. flashruntime/service/auth.py +180 -0
  58. flashruntime/service/checkpoints.py +90 -0
  59. flashruntime/service/cli.py +167 -0
  60. flashruntime/service/dashboard.py +193 -0
  61. flashruntime/service/ledger.py +101 -0
  62. flashruntime/service/modea.py +821 -0
  63. flashruntime/strategies/__init__.py +156 -0
  64. flashruntime/strategies/command.py +56 -0
  65. flashruntime/torch/__init__.py +274 -0
  66. flashruntime/viewer/__init__.py +20 -0
  67. flashruntime/viewer/_docs/benchmarks.html +771 -0
  68. flashruntime/viewer/_docs/concepts/architecture.html +302 -0
  69. flashruntime/viewer/_docs/get-started.html +263 -0
  70. flashruntime/viewer/_docs/guides/federated-averaging.html +363 -0
  71. flashruntime/viewer/_docs/guides/huggingface.html +223 -0
  72. flashruntime/viewer/_docs/guides/jobspec-and-isolation.html +271 -0
  73. flashruntime/viewer/_docs/guides/pytorch.html +313 -0
  74. flashruntime/viewer/_docs/guides/sklearn.html +232 -0
  75. flashruntime/viewer/_docs/index.html +251 -0
  76. flashruntime/viewer/_docs/reference/cli.html +254 -0
  77. flashruntime/viewer/_docs/reference/integrations.html +240 -0
  78. flashruntime/viewer/_docs/reference/sdk.html +341 -0
  79. flashruntime/viewer/_docs/reference/torch-helper.html +244 -0
  80. flashruntime/viewer/_docs/search-index.json +1 -0
  81. flashruntime/viewer/_docs/tutorials/convnet.html +571 -0
  82. flashruntime/viewer/_docs/tutorials/fault-tolerance.html +375 -0
  83. flashruntime/viewer/_docs/tutorials/sklearn-sweeps.html +278 -0
  84. flashruntime/viewer/flowmap.py +307 -0
  85. flashruntime/viewer/page.py +594 -0
  86. flashruntime/viewer/server.py +134 -0
  87. flashruntime/viewer/state.py +250 -0
  88. flashruntime/workloads/__init__.py +6 -0
  89. flashruntime/workloads/command.py +127 -0
  90. flashruntime-0.3.0.dist-info/METADATA +365 -0
  91. flashruntime-0.3.0.dist-info/RECORD +95 -0
  92. flashruntime-0.3.0.dist-info/WHEEL +5 -0
  93. flashruntime-0.3.0.dist-info/entry_points.txt +2 -0
  94. flashruntime-0.3.0.dist-info/licenses/LICENSE +202 -0
  95. 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 &amp; 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
+ &quot;train.py&quot;,
131
+ {&quot;model&quot;: [&quot;logreg&quot;, &quot;rf&quot;], &quot;C&quot;: [0.1, 1.0], &quot;n_estimators&quot;: [50]},
132
+ source=&quot;examples/user_sklearn&quot;,
133
+ ))
134
+ print(f&quot;state={run.state.value} trials={len(run.trials)}&quot;)
135
+ print(&quot;best:&quot;, 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
+ ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" }[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 &amp; 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=&quot;python train.py --epochs 5&quot;,
141
+ source=flash.Source(path=&quot;~/my-project&quot;),
142
+ outputs=flash.OutputSpec(collect=[&quot;metrics.json&quot;]),
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) # &quot;SUCCEEDED&quot; (or &quot;FAILED&quot;)
150
+ print(run.artifacts) # [PosixPath('.../metrics.json'), ...]
151
+ print(run.viewer_url) # http://127.0.0.1:&lt;port&gt; — 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
+ ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" }[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>