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,341 @@
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>Reference: SDK (`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" 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 active" href="sdk.html">Reference: SDK (`flashruntime`)</a>
100
+ <a class="nav-link" href="integrations.html">Reference: integrations (`flashruntime.integrations`)</a>
101
+ <a class="nav-link" href="torch-helper.html">Reference: torch helper (`flashruntime.torch`)</a>
102
+ <a class="nav-link" href="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="reference-sdk-flashruntime">Reference: SDK (<code>flashruntime</code>)</h1>
107
+ <p>The <code>flashruntime</code> top-level package. The core is pydantic-only — <code>import
108
+ flashruntime</code> pulls in <strong>only pydantic</strong>; the bring-your-own-code helpers
109
+ (<code>submit</code>, <code>CommandWorkload</code>, <code>integrations</code>) resolve lazily so the planner
110
+ stays a minimal import.</p>
111
+ <p>Signatures below are exact. Each entry says, in one line, <em>why it exists</em>.</p>
112
+ <hr />
113
+ <h2 id="running-a-workload">Running a workload</h2>
114
+ <h3 id="flashsubmit"><code>flash.submit</code></h3>
115
+ <div class="codeblock"><button class="copybtn" type="button" aria-label="Copy code to clipboard">Copy</button><pre><code class="language-python">def submit(workload, output_dir=None, wait=True, max_restarts=0, watch=None) -&gt; Run: ...
116
+ </code></pre></div>
117
+ <p>The local entry point — compiles a <code>CommandWorkload</code> into a launch spec, runs
118
+ it as a real subprocess (once per param set), collects artifacts, and returns a
119
+ <code>Run</code>.</p>
120
+ <ul>
121
+ <li><code>output_dir</code> — where <code>run.json</code> and artifacts land; a temp dir if <code>None</code>.
122
+ Reusing the <strong>same</strong> dir reuses the job id, so a checkpointed script resumes.</li>
123
+ <li><code>wait</code> — <code>True</code> drives the launch loop inline and returns a finished <code>Run</code>;
124
+ <code>False</code> returns immediately and drives it on a daemon thread (watch it live).</li>
125
+ <li><code>max_restarts</code> — the automatic fault-tolerance budget (default <code>0</code> = no
126
+ retry). A FAILED attempt is classified and run against the versioned recovery
127
+ policy; a deterministic app error fails fast, anything else relaunches from
128
+ the job-scoped checkpoint up to this many times.</li>
129
+ <li><code>watch</code> — open the live viewer and record its URL on <code>run.viewer_url</code>;
130
+ <code>None</code> (default) auto-decides (on at an interactive terminal, off in
131
+ pipes/CI).</li>
132
+ </ul>
133
+ <h3 id="run"><code>Run</code></h3>
134
+ <p>The result handle, fully populated by the time a <code>wait=True</code> <code>submit()</code> returns.</p>
135
+ <table>
136
+ <thead>
137
+ <tr>
138
+ <th>Attribute / method</th>
139
+ <th>Meaning</th>
140
+ </tr>
141
+ </thead>
142
+ <tbody>
143
+ <tr>
144
+ <td><code>run.state</code></td>
145
+ <td><code>LaunchState</code> — <code>SUCCEEDED</code> if every task succeeded, else <code>FAILED</code></td>
146
+ </tr>
147
+ <tr>
148
+ <td><code>run.trials</code></td>
149
+ <td>list of parsed <code>metrics.json</code> dicts (one per task; fan-out merges its <code>params</code>)</td>
150
+ </tr>
151
+ <tr>
152
+ <td><code>run.artifacts</code></td>
153
+ <td>list of collected file <code>Path</code>s</td>
154
+ </tr>
155
+ <tr>
156
+ <td><code>run.output_dir</code></td>
157
+ <td>root the run wrote under</td>
158
+ </tr>
159
+ <tr>
160
+ <td><code>run.viewer_url</code></td>
161
+ <td>live-page URL if <code>watch</code> opened one, else <code>None</code></td>
162
+ </tr>
163
+ <tr>
164
+ <td><code>run.events</code></td>
165
+ <td>snapshot copy of the append-only event log</td>
166
+ </tr>
167
+ <tr>
168
+ <td><code>run.attempts</code></td>
169
+ <td>snapshot copy of the per-launch attempt rows</td>
170
+ </tr>
171
+ <tr>
172
+ <td><code>run.run_json_path</code></td>
173
+ <td>path to the <code>viewer_v1</code> <code>run.json</code> the Run mirrors itself to</td>
174
+ </tr>
175
+ <tr>
176
+ <td><code>run.wait(timeout=None)</code></td>
177
+ <td>block until terminal (event-based, no poll); returns the state</td>
178
+ </tr>
179
+ <tr>
180
+ <td><code>run.logs(tail_lines=200)</code></td>
181
+ <td>captured stdout+stderr (tail)</td>
182
+ </tr>
183
+ <tr>
184
+ <td><code>run.best_trial(metric=None, maximize=None)</code></td>
185
+ <td>best trial by <code>metric</code> (defaults from the workload's <code>OutputSpec</code>); <code>None</code> if none reported it</td>
186
+ </tr>
187
+ </tbody>
188
+ </table>
189
+ <hr />
190
+ <h2 id="describing-a-workload">Describing a workload</h2>
191
+ <h3 id="flashcommandworkload"><code>flash.CommandWorkload</code></h3>
192
+ <div class="codeblock"><button class="copybtn" type="button" aria-label="Copy code to clipboard">Copy</button><pre><code class="language-python">class CommandWorkload(BaseModel):
193
+ command: str | list[str] # shlex-split (NO shell) or an argv list
194
+ source: Source = Source() # where the user's code lives
195
+ image: ImageSpec | None = None # pinned image (required only for the service path)
196
+ env: dict[str, str] = {}
197
+ inputs: dict[str, str] = {} # each value must be an artifact:// URI
198
+ outputs: OutputSpec = OutputSpec()
199
+ resources: Requirements = Requirements() # resource hints (dormant locally; for future providers)
200
+ isolation: IsolationSpec = IsolationSpec()
201
+ mode: str = &quot;auto&quot; # auto | local | independent_tasks | coordinated
202
+ checkpoint: CheckpointPolicy | None = None
203
+ task_params: list[dict] | None = None # {name} placeholders filled per entry (Mode A fan-out)
204
+ </code></pre></div>
205
+ <p>The user-facing description of a "bring your own code" workload — <em>what</em> to run
206
+ and what FlashRuntime should do around it. It never describes <em>how</em> distributed
207
+ math happens; that belongs to your code. <code>command</code> is <code>shlex</code>-split (there is no
208
+ shell — pipes need an explicit <code>command="bash -c '...'"</code>).</p>
209
+ <h3 id="flashsource"><code>flash.Source</code></h3>
210
+ <div class="codeblock"><button class="copybtn" type="button" aria-label="Copy code to clipboard">Copy</button><pre><code class="language-python">class Source(BaseModel):
211
+ path: str = &quot;.&quot; # local dir; ~ is expanded
212
+ git_revision: str | None = None # reserved for remote packaging (later slice)
213
+ </code></pre></div>
214
+ <p>Where the user's code lives — a <code>flash.Source</code>, not a bare string.</p>
215
+ <h3 id="flashoutputspec"><code>flash.OutputSpec</code></h3>
216
+ <div class="codeblock"><button class="copybtn" type="button" aria-label="Copy code to clipboard">Copy</button><pre><code class="language-python">class OutputSpec(BaseModel):
217
+ prefix: str = &quot;artifact://jobs/{job_id}/&quot;
218
+ collect: list[str] = [&quot;metrics.json&quot;] # globs resolved against the script's cwd
219
+ primary_metric: str | None = None # the metrics.json key best_trial() ranks by
220
+ maximize: bool = True
221
+ </code></pre></div>
222
+ <p>What to keep after a run, and how to rank trials. <code>metrics.json</code> in <code>collect</code>
223
+ (the default) is what populates <code>run.trials</code>.</p>
224
+ <hr />
225
+ <h2 id="planning-a-job">Planning a job</h2>
226
+ <h3 id="flashplan"><code>flash.plan</code></h3>
227
+ <div class="codeblock"><button class="copybtn" type="button" aria-label="Copy code to clipboard">Copy</button><pre><code class="language-python">def plan(request: PlanRequest) -&gt; PlanReport: ...
228
+ </code></pre></div>
229
+ <p>Deterministic, explainable strategy selection — turns model + hardware +
230
+ objective into a ranked, explained <code>PlanReport</code> (no cluster required). The
231
+ closed-form arithmetic is framework-import-free.</p>
232
+ <h3 id="flashrender"><code>flash.render</code></h3>
233
+ <div class="codeblock"><button class="copybtn" type="button" aria-label="Copy code to clipboard">Copy</button><pre><code class="language-python">def render(report: PlanReport) -&gt; str: ...
234
+ </code></pre></div>
235
+ <p>Renders a <code>PlanReport</code> as human-readable text (the numbers, the chosen plan,
236
+ and the rejected alternatives with their reasons).</p>
237
+ <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
238
+
239
+ report = flash.plan(flash.PlanRequest(
240
+ workload=flash.TransformerFineTune(model=&quot;Qwen/Qwen2.5-7B&quot;, method=&quot;lora&quot;),
241
+ resources=flash.Resources(gpus=4, gpu_type=&quot;RTX4090&quot;),
242
+ objective=flash.Objective(mode=&quot;balanced&quot;, deadline_minutes=240),
243
+ ))
244
+ print(flash.render(report))
245
+ </code></pre></div>
246
+ <h3 id="plan-inputs">Plan inputs</h3>
247
+ <div class="codeblock"><button class="copybtn" type="button" aria-label="Copy code to clipboard">Copy</button><pre><code class="language-python">class Resources(BaseModel):
248
+ gpus: int = 0 # 0 = CPU-only
249
+ gpu_type: str | None = None # 'A100-40GB', 'L40S', 'RTX4090', ...
250
+ vram_gb: float | None = None # per-GPU VRAM override
251
+ hosts: int = 1
252
+ cpu_ram_gb: float = 32.0
253
+ cpu_cores: int = 8
254
+ hourly_cost_usd_per_gpu: float | None = None
255
+
256
+ class Objective(BaseModel):
257
+ mode: str = &quot;balanced&quot; # cheapest | fastest | balanced | reliable
258
+ max_cost_usd: float | None = None
259
+ deadline_minutes: float | None = None
260
+ allow_quantization: bool = True
261
+ allow_cpu_offload: bool = True
262
+ allow_nvme_offload: bool = False
263
+ </code></pre></div>
264
+ <p>Workload intents: <code>TransformerFineTune</code>, <code>PyTorchTraining</code>, <code>ClassicalML</code>,
265
+ <code>IndependentTasks</code>. See the planner walkthrough (in the repo under
266
+ <code>docs/planner/</code>) for the estimator arithmetic.</p>
267
+ <h3 id="flashrun-designed-not-yet-built"><code>flash.run</code> — designed, not yet built</h3>
268
+ <div class="codeblock"><button class="copybtn" type="button" aria-label="Copy code to clipboard">Copy</button><pre><code class="language-python">def run(plan, coordinator_url=None): ... # raises NotImplementedError
269
+ </code></pre></div>
270
+ <p>The plan-to-execution bridge. It is designed (the docstring describes the
271
+ intended pipeline) but not implemented, so it raises <code>NotImplementedError</code>
272
+ rather than half-running. Today you <code>flash.plan()</code> and submit the JobSpec
273
+ yourself.</p>
274
+ <hr />
275
+ <h2 id="related-references">Related references</h2>
276
+ <ul>
277
+ <li><a href="integrations.html">Integrations</a> — the <code>fr_torch</code> / <code>fr_sklearn</code> / <code>fr_hf</code>
278
+ adapters that build a <code>CommandWorkload</code> for you.</li>
279
+ <li><a href="torch-helper.html">torch helper</a> — the <code>flashruntime.torch</code> surface (three verbs + read-only accessors).</li>
280
+ <li><a href="cli.html">CLI</a> — the <code>flashruntime</code> command.</li>
281
+ </ul></main>
282
+ </div>
283
+
284
+ <script>
285
+ // ---- client-side search: fetch the builder's index, filter as you type -----
286
+ // (<=60 lines, vanilla JS, no external anything — the index is a sibling file.)
287
+ let INDEX = [];
288
+ // Path from THIS page back to the site root. The index stores root-relative
289
+ // URLs (e.g. "tutorials/deep.html"); we fetch it and build result links
290
+ // against ROOT so a page in a subdir resolves them too (see build_docs.py).
291
+ const ROOT = "../";
292
+ const q = document.getElementById("q");
293
+ const results = document.getElementById("results");
294
+ const esc = (s) => String(s).replace(/[&<>"]/g, (c) =>
295
+ ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" }[c]));
296
+ fetch(ROOT + "search-index.json").then((r) => r.json()).then((d) => { INDEX = d; }).catch(() => {});
297
+
298
+ // A short context window around the first match, so a hit shows WHY it matched.
299
+ function snippet(text, needle) {
300
+ const i = text.toLowerCase().indexOf(needle);
301
+ if (i < 0) return "";
302
+ const start = Math.max(0, i - 32);
303
+ return (start > 0 ? "…" : "") + text.slice(start, i + needle.length + 44).trim() + "…";
304
+ }
305
+ function runSearch() {
306
+ const needle = q.value.trim().toLowerCase();
307
+ if (!needle) { results.className = ""; results.innerHTML = ""; return; }
308
+ const hits = INDEX.map((p) => {
309
+ const inTitle = p.title.toLowerCase().includes(needle);
310
+ const inText = p.text.toLowerCase().includes(needle);
311
+ if (!inTitle && !inText) return null;
312
+ return { url: p.url, title: p.title, snip: inText ? snippet(p.text, needle) : "" };
313
+ }).filter(Boolean).slice(0, 20);
314
+ results.className = "open";
315
+ results.innerHTML = hits.length
316
+ ? hits.map((h) => '<a href="' + ROOT + h.url + '"><b>' + esc(h.title) + "</b>" +
317
+ (h.snip ? "<span>" + esc(h.snip) + "</span>" : "") + "</a>").join("")
318
+ : '<div class="nohit">no matches</div>';
319
+ }
320
+ q.addEventListener("input", runSearch);
321
+ q.addEventListener("focus", runSearch);
322
+ document.addEventListener("click", (e) => {
323
+ if (!e.target.closest(".search")) { results.className = ""; } // dismiss on outside click
324
+ });
325
+ document.addEventListener("keydown", (e) => { // "/" focuses search, like PyTorch docs
326
+ if (e.key === "/" && document.activeElement !== q) { e.preventDefault(); q.focus(); }
327
+ });
328
+
329
+ // ---- copy buttons: read the <pre>'s own text into the clipboard ------------
330
+ document.querySelectorAll(".copybtn").forEach((btn) => {
331
+ btn.addEventListener("click", () => {
332
+ const code = btn.parentElement.querySelector("pre").innerText;
333
+ navigator.clipboard.writeText(code).then(() => {
334
+ const was = btn.textContent; btn.textContent = "Copied";
335
+ setTimeout(() => { btn.textContent = was; }, 1200);
336
+ }).catch(() => {});
337
+ });
338
+ });
339
+ </script>
340
+ </body>
341
+ </html>
@@ -0,0 +1,244 @@
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>Reference: torch helper (`flashruntime.torch`) — 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="../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="sdk.html">Reference: SDK (`flashruntime`)</a>
100
+ <a class="nav-link" href="integrations.html">Reference: integrations (`flashruntime.integrations`)</a>
101
+ <a class="nav-link active" href="torch-helper.html">Reference: torch helper (`flashruntime.torch`)</a>
102
+ <a class="nav-link" href="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="reference-torch-helper-flashruntimetorch">Reference: torch helper (<code>flashruntime.torch</code>)</h1>
107
+ <p>The optional in-training-script helper: one import
108
+ (<code>import flashruntime.torch as ft</code>) makes a PyTorch script both
109
+ launch-anywhere and fault-tolerant. <code>torch</code> is imported <strong>inside</strong> these
110
+ functions only — FlashRuntime's core never depends on it.</p>
111
+ <p>The surface is three verbs plus read-only launch-fact accessors — a
112
+ deliberate guardrail (ADR-0003: do not rebuild Accelerate). The boundary is
113
+ <em>capability</em>, not count: there are no FSDP policies, no autocast, no
114
+ DeepSpeed config here, and there never will be. Signatures are exact; each
115
+ says, in one line, <em>why it exists</em>.</p>
116
+ <div class="codeblock"><button class="copybtn" type="button" aria-label="Copy code to clipboard">Copy</button><pre><code class="language-python">def prepare(model, optimizer=None, dataloader=None): ...
117
+ def checkpoint(model, optimizer=None, *, step, every=None) -&gt; None: ...
118
+ def log_metrics(metrics: dict) -&gt; None: ...
119
+ def start_step() -&gt; int: ...
120
+ def rank() -&gt; int: ...
121
+ def world_size() -&gt; int: ...
122
+ def is_main() -&gt; bool: ...
123
+ def device() -&gt; str: ... # &quot;cpu&quot; or &quot;cuda:N&quot; — where prepare() put the model
124
+ def backend() -&gt; str | None: ... # &quot;gloo&quot;/&quot;nccl&quot;, None when single-process
125
+ </code></pre></div>
126
+ <hr />
127
+ <h3 id="preparemodel-optimizernone-dataloadernone"><code>prepare(model, optimizer=None, dataloader=None)</code></h3>
128
+ <p>Wires distributed execution and restores the newest valid checkpoint; returns
129
+ the possibly-wrapped <code>(model, optimizer, dataloader)</code> triple.</p>
130
+ <ul>
131
+ <li>Launched distributed (<code>WORLD_SIZE &gt; 1</code>): initializes torch's own process
132
+ group (<code>nccl</code> on GPU, <code>gloo</code> on CPU), wraps the model in
133
+ <code>DistributedDataParallel</code>, and swaps the DataLoader's sampler for a seed-0
134
+ <code>DistributedSampler</code> (each rank sees a disjoint, deterministic shard).</li>
135
+ <li>On CUDA: selects <code>nccl</code>, moves the model onto this rank's GPU <strong>before</strong> the
136
+ DDP wrap, and binds <code>device_ids</code> / <code>output_device</code> — you no longer call
137
+ <code>model.to(device)</code>.</li>
138
+ <li>Restores the newest <strong>valid</strong> checkpoint manifest if one exists, setting the
139
+ resume step (read it with <code>start_step()</code>).</li>
140
+ <li>Launched as plain <code>python train.py</code>: a no-op passthrough.</li>
141
+ </ul>
142
+ <blockquote>
143
+ <p>The rebuilt DataLoader carries over <code>batch_size</code>, <code>collate_fn</code>,
144
+ <code>num_workers</code>, and <code>drop_last</code>. <strong><code>shuffle</code> and <code>pin_memory</code> are not carried
145
+ over</strong> — the seed-0 <code>DistributedSampler</code> owns shuffling.</p>
146
+ </blockquote>
147
+ <h3 id="checkpointmodel-optimizernone-step-everynone"><code>checkpoint(model, optimizer=None, *, step, every=None)</code></h3>
148
+ <p>rank 0 writes a resumable checkpoint under the <strong>parts-first / manifest-last</strong>
149
+ contract (the manifest is written last, so a half-written checkpoint is never
150
+ <code>latest_valid</code>). <code>every=N</code> no-ops except on multiples of N. Every rank
151
+ synchronizes on a barrier so no one races past a partial write.</p>
152
+ <h3 id="log_metricsmetrics-dict"><code>log_metrics(metrics: dict)</code></h3>
153
+ <p>rank 0 appends one JSON record per call to <code>metrics.jsonl</code> — the streaming
154
+ history the live page draws as a loss curve. <strong>Never raises</strong> (metrics must
155
+ never kill training). Separate from the final <code>metrics.json</code> your script writes
156
+ for <code>run.trials</code>.</p>
157
+ <h3 id="start_step-int"><code>start_step() -&gt; int</code></h3>
158
+ <p>The first step the loop should run: <code>0</code> fresh, <code>&gt;0</code> after a resume (set by
159
+ <code>prepare()</code> when it restores a checkpoint). Start your loop from it.</p>
160
+ <h3 id="rank-world_size-is_main"><code>rank()</code> / <code>world_size()</code> / <code>is_main()</code></h3>
161
+ <p>The small positional helpers, read from the launch environment: <code>rank()</code> is
162
+ this process's <code>RANK</code> (default <code>0</code>), <code>world_size()</code> is <code>WORLD_SIZE</code> (default
163
+ <code>1</code>), and <code>is_main()</code> is <code>rank() == 0</code> — guard rank-0-only work (writing the
164
+ final <code>metrics.json</code>, printing) with it.</p>
165
+ <hr />
166
+ <h2 id="usage-shape">Usage shape</h2>
167
+ <div class="codeblock"><button class="copybtn" type="button" aria-label="Copy code to clipboard">Copy</button><pre><code class="language-python">import flashruntime.torch as ft
168
+
169
+ model, optimizer, loader = ft.prepare(model, optimizer, loader)
170
+ start = ft.start_step()
171
+
172
+ step = start
173
+ while step &lt; total_steps:
174
+ for x, y in loader:
175
+ ...
176
+ step += 1
177
+ ft.checkpoint(model, optimizer, step=step, every=100)
178
+ ft.log_metrics({&quot;step&quot;: step, &quot;loss&quot;: float(loss)})
179
+
180
+ if ft.is_main():
181
+ ... # write the final metrics.json
182
+ </code></pre></div>
183
+ <p>The full worked example is the <a href="../tutorials/convnet.html">ConvNet tutorial</a>; the
184
+ launch side is the <a href="../guides/pytorch.html">PyTorch guide</a>.</p></main>
185
+ </div>
186
+
187
+ <script>
188
+ // ---- client-side search: fetch the builder's index, filter as you type -----
189
+ // (<=60 lines, vanilla JS, no external anything — the index is a sibling file.)
190
+ let INDEX = [];
191
+ // Path from THIS page back to the site root. The index stores root-relative
192
+ // URLs (e.g. "tutorials/deep.html"); we fetch it and build result links
193
+ // against ROOT so a page in a subdir resolves them too (see build_docs.py).
194
+ const ROOT = "../";
195
+ const q = document.getElementById("q");
196
+ const results = document.getElementById("results");
197
+ const esc = (s) => String(s).replace(/[&<>"]/g, (c) =>
198
+ ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" }[c]));
199
+ fetch(ROOT + "search-index.json").then((r) => r.json()).then((d) => { INDEX = d; }).catch(() => {});
200
+
201
+ // A short context window around the first match, so a hit shows WHY it matched.
202
+ function snippet(text, needle) {
203
+ const i = text.toLowerCase().indexOf(needle);
204
+ if (i < 0) return "";
205
+ const start = Math.max(0, i - 32);
206
+ return (start > 0 ? "…" : "") + text.slice(start, i + needle.length + 44).trim() + "…";
207
+ }
208
+ function runSearch() {
209
+ const needle = q.value.trim().toLowerCase();
210
+ if (!needle) { results.className = ""; results.innerHTML = ""; return; }
211
+ const hits = INDEX.map((p) => {
212
+ const inTitle = p.title.toLowerCase().includes(needle);
213
+ const inText = p.text.toLowerCase().includes(needle);
214
+ if (!inTitle && !inText) return null;
215
+ return { url: p.url, title: p.title, snip: inText ? snippet(p.text, needle) : "" };
216
+ }).filter(Boolean).slice(0, 20);
217
+ results.className = "open";
218
+ results.innerHTML = hits.length
219
+ ? hits.map((h) => '<a href="' + ROOT + h.url + '"><b>' + esc(h.title) + "</b>" +
220
+ (h.snip ? "<span>" + esc(h.snip) + "</span>" : "") + "</a>").join("")
221
+ : '<div class="nohit">no matches</div>';
222
+ }
223
+ q.addEventListener("input", runSearch);
224
+ q.addEventListener("focus", runSearch);
225
+ document.addEventListener("click", (e) => {
226
+ if (!e.target.closest(".search")) { results.className = ""; } // dismiss on outside click
227
+ });
228
+ document.addEventListener("keydown", (e) => { // "/" focuses search, like PyTorch docs
229
+ if (e.key === "/" && document.activeElement !== q) { e.preventDefault(); q.focus(); }
230
+ });
231
+
232
+ // ---- copy buttons: read the <pre>'s own text into the clipboard ------------
233
+ document.querySelectorAll(".copybtn").forEach((btn) => {
234
+ btn.addEventListener("click", () => {
235
+ const code = btn.parentElement.querySelector("pre").innerText;
236
+ navigator.clipboard.writeText(code).then(() => {
237
+ const was = btn.textContent; btn.textContent = "Copied";
238
+ setTimeout(() => { btn.textContent = was; }, 1200);
239
+ }).catch(() => {});
240
+ });
241
+ });
242
+ </script>
243
+ </body>
244
+ </html>