flashruntime 0.3.0__tar.gz → 0.4.0__tar.gz
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.
- {flashruntime-0.3.0/flashruntime.egg-info → flashruntime-0.4.0}/PKG-INFO +1 -1
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/protocol/v1alpha1.py +10 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/recipes/command.py +14 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/scheduler/__init__.py +41 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/service/modea.py +3 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0/flashruntime.egg-info}/PKG-INFO +1 -1
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime.egg-info/SOURCES.txt +3 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/pyproject.toml +6 -5
- flashruntime-0.4.0/tests/test_placement_local_data.py +205 -0
- flashruntime-0.4.0/tests/test_protocol_local_datasets.py +52 -0
- flashruntime-0.4.0/tests/test_recipe_local_inputs.py +68 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/LICENSE +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/README.md +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashml_workloads/__init__.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashml_workloads/fedavg_driver.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashml_workloads/fedavg_weights.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashml_workloads/fedavg_worker.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashml_workloads/kmeans_driver.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashml_workloads/kmeans_shard.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashml_workloads/sgd_trainer.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashml_workloads/sharded_kmeans.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashml_workloads/sklearn_trial.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/__init__.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/artifacts/__init__.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/artifacts/store.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/backends/__init__.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/backends/base.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/backends/kuberay.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/checkpoint/__init__.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/checkpoint/catalog.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/checkpoint/local.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/checkpoint/store.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/integrations/__init__.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/integrations/huggingface.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/integrations/pytorch.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/integrations/sklearn.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/launchers/__init__.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/launchers/local.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/leases/__init__.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/leases/manager.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/leases/sqlite_store.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/leases/store.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/monitor/__init__.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/monitor/sampler.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/planner/__init__.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/planner/candidates.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/planner/catalog.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/planner/comm.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/planner/explain.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/planner/memory.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/planner/resolve.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/planner/selector.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/planner/timecost.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/profiling/__init__.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/protocol/__init__.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/protocol/plan_v1alpha1.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/providers/__init__.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/py.typed +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/recipes/__init__.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/recovery/__init__.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/recovery/policy.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/recovery/signals.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/recovery/taxonomy.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/sdk.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/service/__init__.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/service/app.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/service/auth.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/service/checkpoints.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/service/cli.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/service/dashboard.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/service/ledger.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/strategies/__init__.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/strategies/command.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/torch/__init__.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/viewer/__init__.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/viewer/_docs/benchmarks.html +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/viewer/_docs/concepts/architecture.html +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/viewer/_docs/get-started.html +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/viewer/_docs/guides/federated-averaging.html +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/viewer/_docs/guides/huggingface.html +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/viewer/_docs/guides/jobspec-and-isolation.html +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/viewer/_docs/guides/pytorch.html +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/viewer/_docs/guides/sklearn.html +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/viewer/_docs/index.html +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/viewer/_docs/reference/cli.html +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/viewer/_docs/reference/integrations.html +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/viewer/_docs/reference/sdk.html +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/viewer/_docs/reference/torch-helper.html +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/viewer/_docs/search-index.json +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/viewer/_docs/tutorials/convnet.html +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/viewer/_docs/tutorials/fault-tolerance.html +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/viewer/_docs/tutorials/sklearn-sweeps.html +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/viewer/flowmap.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/viewer/page.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/viewer/server.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/viewer/state.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/workloads/__init__.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/workloads/command.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime.egg-info/dependency_links.txt +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime.egg-info/entry_points.txt +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime.egg-info/requires.txt +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime.egg-info/top_level.txt +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/setup.cfg +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/tests/test_auto_recovery.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/tests/test_benchmarks.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/tests/test_checkpoint.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/tests/test_checkpoint_local.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/tests/test_cli_submit.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/tests/test_documentation.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/tests/test_examples_e2e.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/tests/test_fedavg_convergence.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/tests/test_fedavg_driver.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/tests/test_fedavg_weights.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/tests/test_fedavg_worker.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/tests/test_gpu_e2e.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/tests/test_integrations.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/tests/test_interfaces.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/tests/test_kmeans_leases.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/tests/test_kmeans_workload.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/tests/test_kuberay_backend.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/tests/test_launcher_local.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/tests/test_leases.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/tests/test_leases_scope.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/tests/test_leases_sqlite.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/tests/test_monitor_sampler.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/tests/test_planner.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/tests/test_protocol_v1alpha1.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/tests/test_recipes_command.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/tests/test_recovery.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/tests/test_run_json.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/tests/test_scheduler_isolation.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/tests/test_sdk_submit.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/tests/test_service_auth.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/tests/test_service_auth_startup.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/tests/test_service_checkpoints.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/tests/test_service_command_recipe.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/tests/test_service_delegation.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/tests/test_service_fedavg.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/tests/test_service_modea.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/tests/test_service_write_scope.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/tests/test_sgd_trainer.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/tests/test_strategy_command.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/tests/test_torch_helper.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/tests/test_viewer_flowmap.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/tests/test_viewer_page.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/tests/test_viewer_server.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/tests/test_viewer_state.py +0 -0
- {flashruntime-0.3.0 → flashruntime-0.4.0}/tests/test_workloads_command.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flashruntime
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Open fault-tolerant distributed ML runtime: strategy planning, job protocol, leases, checkpointing, and recovery across heterogeneous compute.
|
|
5
5
|
Author: Zolli Labs
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -297,6 +297,16 @@ class NodeRegistration(BaseModel):
|
|
|
297
297
|
#: keeps receiving module work; only a node that explicitly opts into
|
|
298
298
|
#: an argv-only runner sets this False (see scheduler.IsolationAwarePlacement).
|
|
299
299
|
module_capable: bool = True
|
|
300
|
+
#: Names of datasets this node holds locally and offers to jobs. Names
|
|
301
|
+
#: only — never paths: a host path is host-private and must not travel to
|
|
302
|
+
#: the coordinator. Defaults to an empty list, so a node advertises
|
|
303
|
+
#: nothing until its operator explicitly maps a dataset: an agent that has
|
|
304
|
+
#: not opted in must never be eligible for local-data work, and an absent
|
|
305
|
+
#: field on an already-deployed agent's registration must read as "offers
|
|
306
|
+
#: nothing" rather than "offers anything" (security fields fail closed).
|
|
307
|
+
#: `default_factory` and not a bare `[]` — one shared list would let a
|
|
308
|
+
#: single node's advertisement leak into every other node's registration.
|
|
309
|
+
local_datasets: list[str] = Field(default_factory=list)
|
|
300
310
|
pool: str = "local"
|
|
301
311
|
runtime_profile: str = "kubernetes"
|
|
302
312
|
labels: dict[str, str] = Field(default_factory=dict)
|
|
@@ -146,6 +146,20 @@ class CommandRecipe(WorkloadRecipe):
|
|
|
146
146
|
# for byte. Emitting `[]` would mean the same thing today but
|
|
147
147
|
# would stop exercising that path.
|
|
148
148
|
payload["unpack_inputs"] = list(p["unpack_inputs"])
|
|
149
|
+
if p.get("local_inputs") is not None:
|
|
150
|
+
# Labels for host-supplied directories. This forward is what
|
|
151
|
+
# makes the local-data feature real: `IsolationAwarePlacement`
|
|
152
|
+
# reads `task.payload["local_inputs"]` to decide eligibility,
|
|
153
|
+
# and flashnode reads it again to know what to mount.
|
|
154
|
+
#
|
|
155
|
+
# Dropping it does NOT fail closed. The gate sees a task
|
|
156
|
+
# requiring nothing, places it on any node, and flashnode
|
|
157
|
+
# mounts nothing — so the task runs without the data it asked
|
|
158
|
+
# for. Both ends of this hop have tests that pass while it is
|
|
159
|
+
# broken, because each constructs the payload directly.
|
|
160
|
+
#
|
|
161
|
+
# `list(...)` so a payload never aliases the caller's spec.
|
|
162
|
+
payload["local_inputs"] = list(p["local_inputs"])
|
|
149
163
|
tasks.append(
|
|
150
164
|
TaskSpec(
|
|
151
165
|
task_id=task_id,
|
|
@@ -143,6 +143,35 @@ class IsolationAwarePlacement(PlacementPolicy):
|
|
|
143
143
|
cut every already-deployed node off from module work the moment this
|
|
144
144
|
field shipped, without anyone having opted out of anything.
|
|
145
145
|
|
|
146
|
+
A fourth gate applies to tasks whose payload lists `local_inputs` (data
|
|
147
|
+
the host holds and never uploads): the claiming node must advertise
|
|
148
|
+
EVERY one of those names in its `local_datasets` capability. It shares
|
|
149
|
+
the argv gate's fail-closed polarity, not the module gate's, because
|
|
150
|
+
the host's data is the thing being protected — an absent, `None`, or
|
|
151
|
+
wrongly-typed capability counts as NOT capable:
|
|
152
|
+
|
|
153
|
+
- The capability must be a genuine *list* of names. A bare string does
|
|
154
|
+
not count, even when it looks right: ``"patients" in "patients"`` is
|
|
155
|
+
True in Python, so accepting a string would let a node advertise
|
|
156
|
+
every dataset whose name is a substring of anything it said. That is
|
|
157
|
+
the same class of type confusion `sandbox_capable is True` avoids
|
|
158
|
+
for booleans.
|
|
159
|
+
- The requirement itself must be a list. A `local_inputs` payload that
|
|
160
|
+
is present but not a list (e.g. the bare string ``"patients"``) makes
|
|
161
|
+
the task ineligible everywhere — fail closed without crashing the
|
|
162
|
+
predicate, exactly as a non-dict isolation payload does.
|
|
163
|
+
- An empty `local_inputs` requires nothing and so runs anywhere, like
|
|
164
|
+
tier ``"standard"``.
|
|
165
|
+
|
|
166
|
+
The `allowFallback` waiver does NOT apply here, for the same reason it
|
|
167
|
+
does not waive the argv gate: it waives the sandbox *tier* requirement
|
|
168
|
+
and nothing else. A waiver is the submitter's statement about their own
|
|
169
|
+
isolation posture; the local-data gate protects the HOST's data, which
|
|
170
|
+
the submitter has no standing to waive. A task placed on a node that
|
|
171
|
+
never advertised the dataset would fail on the agent anyway (the label
|
|
172
|
+
is not in its map) — failing closed here makes it fail before anything
|
|
173
|
+
touches the data, and before an attempt is burned.
|
|
174
|
+
|
|
146
175
|
Everything genuinely standard keeps the fail-open placement default."""
|
|
147
176
|
|
|
148
177
|
def eligible(self, task: TaskSpec, node: NodeView) -> bool:
|
|
@@ -158,6 +187,18 @@ class IsolationAwarePlacement(PlacementPolicy):
|
|
|
158
187
|
# into the same node's path until attempts are exhausted.
|
|
159
188
|
if "module" in task.payload and node.get("module_capable") is False:
|
|
160
189
|
return False
|
|
190
|
+
# Fail-closed like the argv gate, and checked before the allowFallback
|
|
191
|
+
# waiver below for the same reason: the waiver covers the sandbox tier
|
|
192
|
+
# only, and a submitter cannot waive their way onto a host's data.
|
|
193
|
+
local_inputs = task.payload.get("local_inputs")
|
|
194
|
+
if local_inputs is not None:
|
|
195
|
+
if not isinstance(local_inputs, list):
|
|
196
|
+
return False # type-confused requirement ⇒ fail closed, no crash
|
|
197
|
+
advertised = node.get("local_datasets")
|
|
198
|
+
if local_inputs and not isinstance(advertised, list):
|
|
199
|
+
return False # absent/None/type-confused capability ⇒ not capable
|
|
200
|
+
if any(name not in advertised for name in local_inputs):
|
|
201
|
+
return False
|
|
161
202
|
isolation = task.payload.get("isolation")
|
|
162
203
|
if isolation is None:
|
|
163
204
|
return True # no isolation payload ⇒ standard, runs anywhere
|
|
@@ -643,6 +643,9 @@ def build_router(state: ModeAState) -> APIRouter:
|
|
|
643
643
|
"sandbox_capable": entry.registration.sandbox_capable,
|
|
644
644
|
"argv_capable": entry.registration.argv_capable,
|
|
645
645
|
"module_capable": entry.registration.module_capable,
|
|
646
|
+
# Names only — the host paths behind them never reach us, and the
|
|
647
|
+
# placement gate needs the names to place local-data work at all.
|
|
648
|
+
"local_datasets": entry.registration.local_datasets,
|
|
646
649
|
"capabilities": entry.registration.capabilities.model_dump(),
|
|
647
650
|
}
|
|
648
651
|
lease = manager.claim(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flashruntime
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Open fault-tolerant distributed ML runtime: strategy planning, job protocol, leases, checkpointing, and recovery across heterogeneous compute.
|
|
5
5
|
Author: Zolli Labs
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -118,8 +118,11 @@ tests/test_leases.py
|
|
|
118
118
|
tests/test_leases_scope.py
|
|
119
119
|
tests/test_leases_sqlite.py
|
|
120
120
|
tests/test_monitor_sampler.py
|
|
121
|
+
tests/test_placement_local_data.py
|
|
121
122
|
tests/test_planner.py
|
|
123
|
+
tests/test_protocol_local_datasets.py
|
|
122
124
|
tests/test_protocol_v1alpha1.py
|
|
125
|
+
tests/test_recipe_local_inputs.py
|
|
123
126
|
tests/test_recipes_command.py
|
|
124
127
|
tests/test_recovery.py
|
|
125
128
|
tests/test_run_json.py
|
|
@@ -5,11 +5,12 @@ build-backend = "setuptools.build_meta"
|
|
|
5
5
|
|
|
6
6
|
[project]
|
|
7
7
|
name = "flashruntime"
|
|
8
|
-
# 0.
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
|
|
8
|
+
# 0.4.0 adds NodeRegistration.local_datasets — a new wire field, so consumers
|
|
9
|
+
# must pin a range that includes it. flashnode’s floor moves to >=0.4,<0.5.
|
|
10
|
+
# (0.3.0 was the first published release; 0.1.0 and 0.2.0 were declared here
|
|
11
|
+
# but never reached PyPI.) Released from the Zolli-Labs/flashml monorepo by
|
|
12
|
+
# pushing a `flashruntime-v0.4.0` tag.
|
|
13
|
+
version = "0.4.0"
|
|
13
14
|
description = "Open fault-tolerant distributed ML runtime: strategy planning, job protocol, leases, checkpointing, and recovery across heterogeneous compute."
|
|
14
15
|
readme = "README.md"
|
|
15
16
|
# PEP 639 SPDX expression (string form) — replaces the deprecated
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"""The fourth fail-closed placement gate: a task that names `local_inputs` may
|
|
2
|
+
only be leased to a node advertising every one of those names in its
|
|
3
|
+
`local_datasets` capability (AGENTS.md rule 3).
|
|
4
|
+
|
|
5
|
+
The data never leaves the host, so placement is the only thing standing between
|
|
6
|
+
a task and a machine that cannot serve it. An absent, None, or wrongly-typed
|
|
7
|
+
capability counts as NOT capable, exactly like `sandbox_capable`/`argv_capable`.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import pytest
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _local_task(local_inputs=("patients",), **payload_extra):
|
|
16
|
+
"""A task requiring local datasets. `local_inputs` is passed through RAW so
|
|
17
|
+
tests can poison it with type-confused values the protocol would never
|
|
18
|
+
emit (the same trick `_task_raw` plays on the isolation payload)."""
|
|
19
|
+
from flashruntime.protocol.v1alpha1 import TaskSpec
|
|
20
|
+
|
|
21
|
+
payload = {"module": "flashml_workloads.sklearn_trial"}
|
|
22
|
+
if local_inputs is not None:
|
|
23
|
+
payload["local_inputs"] = local_inputs
|
|
24
|
+
payload.update(payload_extra)
|
|
25
|
+
return TaskSpec(
|
|
26
|
+
task_id="task-000", job_id="job-a", commit_key="job-a/task-000/m.json",
|
|
27
|
+
payload=payload,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_node_advertising_a_superset_is_eligible():
|
|
32
|
+
from flashruntime.scheduler import IsolationAwarePlacement
|
|
33
|
+
|
|
34
|
+
node = {"node_id": "n1", "local_datasets": ["patients", "labs"]}
|
|
35
|
+
assert IsolationAwarePlacement().eligible(_local_task(["patients"]), node) is True
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_node_advertising_every_required_name_is_eligible():
|
|
39
|
+
"""Two requirements, both advertised — the gate is per-name, not per-task."""
|
|
40
|
+
from flashruntime.scheduler import IsolationAwarePlacement
|
|
41
|
+
|
|
42
|
+
node = {"node_id": "n1", "local_datasets": ["labs", "patients"]}
|
|
43
|
+
task = _local_task(["patients", "labs"])
|
|
44
|
+
assert IsolationAwarePlacement().eligible(task, node) is True
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_node_advertising_only_some_required_names_is_ineligible():
|
|
48
|
+
from flashruntime.scheduler import IsolationAwarePlacement
|
|
49
|
+
|
|
50
|
+
node = {"node_id": "n1", "local_datasets": ["patients"]}
|
|
51
|
+
task = _local_task(["patients", "labs"])
|
|
52
|
+
assert IsolationAwarePlacement().eligible(task, node) is False
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_node_advertising_a_different_dataset_is_ineligible():
|
|
56
|
+
from flashruntime.scheduler import IsolationAwarePlacement
|
|
57
|
+
|
|
58
|
+
node = {"node_id": "n1", "local_datasets": ["labs"]}
|
|
59
|
+
assert IsolationAwarePlacement().eligible(_local_task(["patients"]), node) is False
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@pytest.mark.parametrize(
|
|
63
|
+
"advertised",
|
|
64
|
+
[
|
|
65
|
+
pytest.param([], id="empty-list"),
|
|
66
|
+
pytest.param(None, id="explicit-none"),
|
|
67
|
+
pytest.param("patients", id="bare-string-type-confusion"),
|
|
68
|
+
pytest.param({"patients": "/srv/data"}, id="dict-type-confusion"),
|
|
69
|
+
pytest.param(("patients",), id="tuple-type-confusion"),
|
|
70
|
+
pytest.param(True, id="truthy-boolean"),
|
|
71
|
+
],
|
|
72
|
+
)
|
|
73
|
+
def test_ineligible_unless_the_capability_is_a_list_containing_the_name(advertised):
|
|
74
|
+
"""Fail closed on absence AND on type confusion. The bare string
|
|
75
|
+
"patients" must not count: `"patients" in "patients"` is True in Python,
|
|
76
|
+
which is exactly the substring trap `sandbox_capable is True` avoids for
|
|
77
|
+
booleans. Only a genuine list of names advertises anything."""
|
|
78
|
+
from flashruntime.scheduler import IsolationAwarePlacement
|
|
79
|
+
|
|
80
|
+
node = {"node_id": "n1", "local_datasets": advertised}
|
|
81
|
+
assert IsolationAwarePlacement().eligible(_local_task(["patients"]), node) is False
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_node_with_no_local_datasets_field_at_all_is_ineligible():
|
|
85
|
+
"""An already-deployed agent whose registration predates the field
|
|
86
|
+
advertises nothing — absent must read as "offers nothing", never as
|
|
87
|
+
"offers anything"."""
|
|
88
|
+
from flashruntime.scheduler import IsolationAwarePlacement
|
|
89
|
+
|
|
90
|
+
node = {"node_id": "n1"} # no local_datasets key at all
|
|
91
|
+
assert IsolationAwarePlacement().eligible(_local_task(["patients"]), node) is False
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def test_allow_fallback_cannot_bypass_the_local_data_gate():
|
|
95
|
+
"""allowFallback waives the sandbox-tier requirement only. It must not also
|
|
96
|
+
waive the local-data gate — the same reasoning that stops it waiving the
|
|
97
|
+
argv gate: a submitter cannot grant themselves data a host never offered."""
|
|
98
|
+
from flashruntime.scheduler import IsolationAwarePlacement
|
|
99
|
+
|
|
100
|
+
task = _local_task(
|
|
101
|
+
["patients"], isolation={"tier": "sandboxed", "allowFallback": True}
|
|
102
|
+
)
|
|
103
|
+
node = {"node_id": "n1", "sandbox_capable": False, "local_datasets": []}
|
|
104
|
+
assert IsolationAwarePlacement().eligible(task, node) is False
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def test_allow_fallback_does_not_bypass_it_on_a_node_missing_the_field():
|
|
108
|
+
from flashruntime.scheduler import IsolationAwarePlacement
|
|
109
|
+
|
|
110
|
+
task = _local_task(
|
|
111
|
+
["patients"], isolation={"tier": "sandboxed", "allowFallback": True}
|
|
112
|
+
)
|
|
113
|
+
assert IsolationAwarePlacement().eligible(task, {"node_id": "n1"}) is False
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def test_type_confused_local_inputs_is_ineligible_everywhere():
|
|
117
|
+
"""A poisoned requirement (present but not a list, e.g. the bare string
|
|
118
|
+
"patients") must fail closed on every node — including one that advertises
|
|
119
|
+
the name — and must never crash the predicate."""
|
|
120
|
+
from flashruntime.scheduler import IsolationAwarePlacement
|
|
121
|
+
|
|
122
|
+
policy = IsolationAwarePlacement()
|
|
123
|
+
node = {"node_id": "n1", "local_datasets": ["patients"]}
|
|
124
|
+
assert policy.eligible(_local_task("patients"), node) is False
|
|
125
|
+
assert policy.eligible(_local_task({"patients": 1}), node) is False
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def test_tasks_without_local_inputs_are_unaffected():
|
|
129
|
+
from flashruntime.scheduler import IsolationAwarePlacement
|
|
130
|
+
|
|
131
|
+
policy = IsolationAwarePlacement()
|
|
132
|
+
task = _local_task(None) # no local_inputs key in the payload at all
|
|
133
|
+
assert policy.eligible(task, {"node_id": "n1"}) is True
|
|
134
|
+
assert policy.eligible(task, {"node_id": "n2", "local_datasets": ["labs"]}) is True
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def test_empty_local_inputs_list_requires_nothing():
|
|
138
|
+
"""`local_inputs: []` is what a job that opted out of the feature compiles
|
|
139
|
+
to — it demands nothing, so it runs anywhere, like tier "standard"."""
|
|
140
|
+
from flashruntime.scheduler import IsolationAwarePlacement
|
|
141
|
+
|
|
142
|
+
assert IsolationAwarePlacement().eligible(_local_task([]), {"node_id": "n1"}) is True
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def test_claim_over_a_local_data_queue_serves_the_clean_task():
|
|
146
|
+
"""A local-data task at the head of the queue must not block a node that
|
|
147
|
+
cannot serve it — the next clean task is still leased, and the local-data
|
|
148
|
+
task is never mis-leased."""
|
|
149
|
+
from flashruntime.leases import LeaseManager
|
|
150
|
+
from flashruntime.protocol.v1alpha1 import TaskSpec
|
|
151
|
+
from flashruntime.scheduler import IsolationAwarePlacement
|
|
152
|
+
|
|
153
|
+
mgr = LeaseManager()
|
|
154
|
+
mgr.add_task(_local_task(["patients"])) # head of queue, needs data
|
|
155
|
+
mgr.add_task(
|
|
156
|
+
TaskSpec(task_id="task-001", job_id="job-a", commit_key="job-a/task-001/m.json")
|
|
157
|
+
)
|
|
158
|
+
policy = IsolationAwarePlacement()
|
|
159
|
+
|
|
160
|
+
lease = mgr.claim("n1", policy=policy, node={"node_id": "n1"})
|
|
161
|
+
assert lease is not None
|
|
162
|
+
assert lease.task_id == "task-001"
|
|
163
|
+
|
|
164
|
+
holder = {"node_id": "n2", "local_datasets": ["patients"]}
|
|
165
|
+
lease2 = mgr.claim("n2", policy=policy, node=holder)
|
|
166
|
+
assert lease2 is not None
|
|
167
|
+
assert lease2.task_id == "task-000"
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def test_claim_endpoint_leases_local_data_work_only_to_the_holder():
|
|
171
|
+
"""End to end over the claim endpoint: the registry's claim-time node view
|
|
172
|
+
must forward the advertised names, or the gate reads an absent capability
|
|
173
|
+
on EVERY node and a local-data task becomes unplaceable everywhere — fail
|
|
174
|
+
closed, but also permanently stuck."""
|
|
175
|
+
import pathlib
|
|
176
|
+
|
|
177
|
+
import fastapi
|
|
178
|
+
from fastapi.testclient import TestClient
|
|
179
|
+
|
|
180
|
+
from flashruntime.leases import LeaseManager
|
|
181
|
+
from flashruntime.service.modea import ModeAState, build_router
|
|
182
|
+
|
|
183
|
+
state = ModeAState(LeaseManager(), artifacts_dir=pathlib.Path("/tmp"))
|
|
184
|
+
app = fastapi.FastAPI()
|
|
185
|
+
app.include_router(build_router(state))
|
|
186
|
+
client = TestClient(app)
|
|
187
|
+
state.manager.add_task(_local_task(["patients"]))
|
|
188
|
+
|
|
189
|
+
def register(node_id: str, local_datasets: list[str]):
|
|
190
|
+
r = client.post(
|
|
191
|
+
"/v1alpha1/nodes/register",
|
|
192
|
+
json={
|
|
193
|
+
"node_id": node_id, "kubernetes_node": "", "hostname": node_id,
|
|
194
|
+
"capabilities": {}, "local_datasets": local_datasets,
|
|
195
|
+
},
|
|
196
|
+
)
|
|
197
|
+
assert r.status_code == 200
|
|
198
|
+
|
|
199
|
+
register("no-data-node", [])
|
|
200
|
+
register("holder-node", ["patients", "labs"])
|
|
201
|
+
|
|
202
|
+
assert client.post("/v1alpha1/leases/claim", json={"node_id": "no-data-node"}).status_code == 204
|
|
203
|
+
r = client.post("/v1alpha1/leases/claim", json={"node_id": "holder-node"})
|
|
204
|
+
assert r.status_code == 200
|
|
205
|
+
assert r.json()["task_id"] == "task-000"
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""`NodeRegistration.local_datasets` — the names of on-host datasets a node
|
|
2
|
+
advertises, and the fail-closed defaulting that governs them.
|
|
3
|
+
|
|
4
|
+
These are protocol tests, not scheduler tests: they pin the *wire* behaviour
|
|
5
|
+
that the placement gate is later allowed to trust. The gate can only fail
|
|
6
|
+
closed if the field itself does.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from flashruntime.protocol.v1alpha1 import NodeRegistration
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def make_registration(**overrides):
|
|
13
|
+
base = {
|
|
14
|
+
"node_id": "node-a",
|
|
15
|
+
"kubernetes_node": "worker-0",
|
|
16
|
+
"hostname": "worker-0.local",
|
|
17
|
+
"capabilities": {"cpu_cores": 4, "os": "linux", "architecture": "amd64"},
|
|
18
|
+
}
|
|
19
|
+
base.update(overrides)
|
|
20
|
+
return base
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_local_datasets_defaults_empty_when_absent_from_the_wire():
|
|
24
|
+
# An agent deployed before this field existed sends a registration with no
|
|
25
|
+
# `local_datasets` key at all. It must decode as advertising nothing, so it
|
|
26
|
+
# is never eligible for local-data work until it is upgraded and opts in
|
|
27
|
+
# explicitly (AGENTS.md rule 3: security-relevant fields fail closed).
|
|
28
|
+
reg = NodeRegistration.model_validate(make_registration())
|
|
29
|
+
assert reg.local_datasets == []
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_local_datasets_round_trips_through_json():
|
|
33
|
+
reg = NodeRegistration.model_validate(
|
|
34
|
+
make_registration(local_datasets=["patients", "labs"])
|
|
35
|
+
)
|
|
36
|
+
restored = NodeRegistration.model_validate_json(reg.model_dump_json())
|
|
37
|
+
assert restored.local_datasets == ["patients", "labs"]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_local_datasets_default_is_not_shared_between_instances():
|
|
41
|
+
# A bare `[]` default would be one list shared by every NodeRegistration
|
|
42
|
+
# ever constructed: one node appending to it would silently advertise its
|
|
43
|
+
# datasets on behalf of every other node in the pool. `default_factory`
|
|
44
|
+
# is what stops that, and this test is what stops someone "simplifying"
|
|
45
|
+
# it back.
|
|
46
|
+
first = NodeRegistration.model_validate(make_registration(node_id="node-a"))
|
|
47
|
+
second = NodeRegistration.model_validate(make_registration(node_id="node-b"))
|
|
48
|
+
|
|
49
|
+
assert first.local_datasets is not second.local_datasets
|
|
50
|
+
|
|
51
|
+
first.local_datasets.append("patients")
|
|
52
|
+
assert second.local_datasets == []
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""`local_inputs` must survive the JobSpec -> task payload hop.
|
|
2
|
+
|
|
3
|
+
The feature is built at both ends of this hop and was broken in the middle.
|
|
4
|
+
`flashml.yaml` puts `local_inputs` into the workload parameters, and
|
|
5
|
+
`IsolationAwarePlacement` reads it from `task.payload` — but `CommandRecipe`
|
|
6
|
+
builds each payload from a fixed key list and dropped every parameter it did
|
|
7
|
+
not recognise, `local_inputs` among them.
|
|
8
|
+
|
|
9
|
+
The failure mode is the dangerous direction. The gate sees no requirement, so
|
|
10
|
+
it does not fail closed and refuse the node — it fails OPEN, places the task
|
|
11
|
+
anywhere, and flashnode mounts nothing. The task then runs without the data it
|
|
12
|
+
asked for.
|
|
13
|
+
|
|
14
|
+
Nothing caught it because both ends' tests construct the intermediate value
|
|
15
|
+
directly: the compile tests assert the parameter lands in the JobSpec, and the
|
|
16
|
+
placement tests build a payload containing `local_inputs` by hand. Each end was
|
|
17
|
+
correct about itself. This test is about the hop between them.
|
|
18
|
+
"""
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from flashruntime.protocol.v1alpha1 import JobSpec
|
|
22
|
+
from flashruntime.recipes.command import CommandRecipe
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _spec(**params) -> JobSpec:
|
|
26
|
+
return JobSpec.model_validate({
|
|
27
|
+
"metadata": {"name": "local-data-job"},
|
|
28
|
+
"spec": {
|
|
29
|
+
"image": {"repository": "ghcr.io/zolli-labs/flashml-python-slim",
|
|
30
|
+
"tag": "2026.08.1"},
|
|
31
|
+
"workload": {
|
|
32
|
+
"type": "command",
|
|
33
|
+
"parameters": {"command": ["python", "train.py"], **params},
|
|
34
|
+
},
|
|
35
|
+
"resources": {"minimumWorkers": 1, "maximumWorkers": 1},
|
|
36
|
+
"isolation": {"tier": "sandboxed", "allowFallback": False},
|
|
37
|
+
},
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_local_inputs_reaches_the_task_payload():
|
|
42
|
+
"""Where the placement gate actually reads it."""
|
|
43
|
+
tasks = CommandRecipe().expand("job-1", _spec(local_inputs=["patients"]))
|
|
44
|
+
|
|
45
|
+
assert tasks, "expected at least one task"
|
|
46
|
+
for task in tasks:
|
|
47
|
+
assert task.payload.get("local_inputs") == ["patients"], (
|
|
48
|
+
"the placement gate reads task.payload['local_inputs']; without it "
|
|
49
|
+
"the gate fails OPEN and the task is placed on a node with no data"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_absent_local_inputs_stays_absent():
|
|
54
|
+
"""Never an empty list — same reasoning as `unpack_inputs` beside it.
|
|
55
|
+
|
|
56
|
+
An omitted key is the path where nothing about placement changes, and
|
|
57
|
+
emitting `[]` would mean the same thing today while quietly ceasing to
|
|
58
|
+
exercise that path."""
|
|
59
|
+
for task in CommandRecipe().expand("job-1", _spec()):
|
|
60
|
+
assert "local_inputs" not in task.payload
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_local_inputs_is_copied_not_aliased():
|
|
64
|
+
"""A payload must not share a mutable list with the caller's spec."""
|
|
65
|
+
names = ["patients"]
|
|
66
|
+
tasks = CommandRecipe().expand("job-1", _spec(local_inputs=names))
|
|
67
|
+
names.append("labs")
|
|
68
|
+
assert tasks[0].payload["local_inputs"] == ["patients"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/viewer/_docs/concepts/architecture.html
RENAMED
|
File without changes
|
|
File without changes
|
{flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/viewer/_docs/guides/federated-averaging.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/viewer/_docs/reference/integrations.html
RENAMED
|
File without changes
|
|
File without changes
|
{flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/viewer/_docs/reference/torch-helper.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/viewer/_docs/tutorials/fault-tolerance.html
RENAMED
|
File without changes
|
{flashruntime-0.3.0 → flashruntime-0.4.0}/flashruntime/viewer/_docs/tutorials/sklearn-sweeps.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|