flashruntime 0.4.3__tar.gz → 0.5.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.4.3 → flashruntime-0.5.0}/PKG-INFO +43 -15
- {flashruntime-0.4.3 → flashruntime-0.5.0}/README.md +40 -14
- flashruntime-0.5.0/flashml_workloads/chunks.py +137 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashml_workloads/fedavg_driver.py +376 -65
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashml_workloads/fedavg_weights.py +96 -43
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashml_workloads/fedavg_worker.py +84 -32
- flashruntime-0.5.0/flashruntime/envelope.py +310 -0
- flashruntime-0.5.0/flashruntime/images/TAG +1 -0
- flashruntime-0.5.0/flashruntime/images/__init__.py +88 -0
- flashruntime-0.5.0/flashruntime/images/python-slim.txt +8 -0
- flashruntime-0.5.0/flashruntime/images/pytorch-cpu.txt +8 -0
- flashruntime-0.5.0/flashruntime/images/pytorch-cuda.txt +8 -0
- flashruntime-0.5.0/flashruntime/images/sklearn.txt +11 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/integrations/sklearn.py +15 -3
- flashruntime-0.5.0/flashruntime/leases/__init__.py +94 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/protocol/v1alpha1.py +45 -1
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/recipes/command.py +78 -2
- flashruntime-0.5.0/flashruntime/recipes/partition.py +142 -0
- flashruntime-0.5.0/flashruntime/reduce.py +199 -0
- flashruntime-0.5.0/flashruntime/retention.py +224 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/scheduler/__init__.py +338 -5
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/sdk.py +4 -3
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/service/app.py +44 -5
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/service/modea.py +675 -54
- flashruntime-0.5.0/flashruntime/validators.py +131 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/viewer/_docs/guides/federated-averaging.html +145 -55
- flashruntime-0.5.0/flashruntime/viewer/_docs/search-index.json +1 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/workloads/command.py +32 -3
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime.egg-info/PKG-INFO +43 -15
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime.egg-info/SOURCES.txt +31 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime.egg-info/requires.txt +3 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/pyproject.toml +9 -2
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_benchmarks.py +710 -0
- flashruntime-0.5.0/tests/test_chunks.py +78 -0
- flashruntime-0.5.0/tests/test_cost_benchmark.py +1216 -0
- flashruntime-0.5.0/tests/test_declared_validators.py +179 -0
- flashruntime-0.5.0/tests/test_envelope.py +295 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_fedavg_convergence.py +29 -19
- flashruntime-0.5.0/tests/test_fedavg_driver.py +1649 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_fedavg_weights.py +65 -10
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_fedavg_worker.py +99 -20
- flashruntime-0.5.0/tests/test_headline.py +118 -0
- flashruntime-0.5.0/tests/test_image_manifest_lookup.py +65 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_integrations.py +41 -2
- flashruntime-0.5.0/tests/test_job_state_partial.py +167 -0
- flashruntime-0.5.0/tests/test_lease_seconds_bound.py +153 -0
- flashruntime-0.5.0/tests/test_placement_dependencies.py +258 -0
- flashruntime-0.5.0/tests/test_placement_reliability.py +295 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_protocol_pools.py +11 -0
- flashruntime-0.5.0/tests/test_recipe_partition.py +257 -0
- flashruntime-0.5.0/tests/test_recipes_command.py +305 -0
- flashruntime-0.5.0/tests/test_reduce.py +223 -0
- flashruntime-0.5.0/tests/test_retention.py +391 -0
- flashruntime-0.5.0/tests/test_service_artifact_delete.py +159 -0
- flashruntime-0.5.0/tests/test_service_fedavg.py +430 -0
- flashruntime-0.5.0/tests/test_service_job_result.py +157 -0
- flashruntime-0.5.0/tests/test_service_reliability.py +279 -0
- flashruntime-0.5.0/tests/test_service_validate_output.py +110 -0
- flashruntime-0.5.0/tests/test_submit_envelope.py +210 -0
- flashruntime-0.5.0/tests/test_submit_retention.py +119 -0
- flashruntime-0.4.3/flashruntime/leases/__init__.py +0 -27
- flashruntime-0.4.3/flashruntime/viewer/_docs/search-index.json +0 -1
- flashruntime-0.4.3/tests/test_fedavg_driver.py +0 -980
- flashruntime-0.4.3/tests/test_recipes_command.py +0 -154
- flashruntime-0.4.3/tests/test_service_fedavg.py +0 -206
- {flashruntime-0.4.3 → flashruntime-0.5.0}/LICENSE +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashml_workloads/__init__.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashml_workloads/kmeans_driver.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashml_workloads/kmeans_shard.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashml_workloads/sgd_trainer.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashml_workloads/sharded_kmeans.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashml_workloads/sklearn_trial.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/__init__.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/artifacts/__init__.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/artifacts/store.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/backends/__init__.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/backends/base.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/backends/kuberay.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/checkpoint/__init__.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/checkpoint/catalog.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/checkpoint/local.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/checkpoint/store.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/integrations/__init__.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/integrations/huggingface.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/integrations/pytorch.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/launchers/__init__.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/launchers/local.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/leases/manager.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/leases/sqlite_store.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/leases/store.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/monitor/__init__.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/monitor/sampler.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/planner/__init__.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/planner/candidates.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/planner/catalog.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/planner/comm.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/planner/explain.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/planner/memory.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/planner/resolve.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/planner/selector.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/planner/timecost.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/profiling/__init__.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/protocol/__init__.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/protocol/plan_v1alpha1.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/providers/__init__.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/py.typed +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/recipes/__init__.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/recovery/__init__.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/recovery/policy.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/recovery/signals.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/recovery/taxonomy.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/service/__init__.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/service/auth.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/service/checkpoints.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/service/cli.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/service/dashboard.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/service/ledger.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/strategies/__init__.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/strategies/command.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/torch/__init__.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/viewer/__init__.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/viewer/_docs/benchmarks.html +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/viewer/_docs/concepts/architecture.html +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/viewer/_docs/get-started.html +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/viewer/_docs/guides/huggingface.html +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/viewer/_docs/guides/jobspec-and-isolation.html +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/viewer/_docs/guides/pytorch.html +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/viewer/_docs/guides/sklearn.html +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/viewer/_docs/index.html +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/viewer/_docs/reference/cli.html +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/viewer/_docs/reference/integrations.html +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/viewer/_docs/reference/sdk.html +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/viewer/_docs/reference/torch-helper.html +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/viewer/_docs/tutorials/convnet.html +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/viewer/_docs/tutorials/fault-tolerance.html +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/viewer/_docs/tutorials/sklearn-sweeps.html +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/viewer/flowmap.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/viewer/page.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/viewer/server.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/viewer/state.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime/workloads/__init__.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime.egg-info/dependency_links.txt +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime.egg-info/entry_points.txt +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/flashruntime.egg-info/top_level.txt +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/setup.cfg +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_auto_recovery.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_checkpoint.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_checkpoint_local.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_cli_submit.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_documentation.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_examples_e2e.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_gpu_e2e.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_interfaces.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_kmeans_leases.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_kmeans_workload.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_kuberay_backend.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_launcher_local.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_leases.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_leases_scope.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_leases_sqlite.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_monitor_sampler.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_placement_exclusion.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_placement_gpu.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_placement_local_data.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_placement_pool.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_placement_trusted_argv.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_planner.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_protocol_evidence.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_protocol_gpu.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_protocol_local_datasets.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_protocol_v1alpha1.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_recipe_gpu.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_recipe_local_inputs.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_recipe_pool.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_recovery.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_run_json.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_scheduler_isolation.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_sdk_submit.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_service_auth.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_service_auth_startup.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_service_checkpoints.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_service_command_recipe.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_service_delegation.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_service_modea.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_service_write_scope.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_sgd_trainer.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_strategy_command.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_torch_helper.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_viewer_flowmap.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_viewer_page.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_viewer_server.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.0}/tests/test_viewer_state.py +0 -0
- {flashruntime-0.4.3 → flashruntime-0.5.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.5.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
|
|
@@ -35,6 +35,8 @@ Provides-Extra: artifacts
|
|
|
35
35
|
Requires-Dist: minio>=7.2; extra == "artifacts"
|
|
36
36
|
Provides-Extra: oss
|
|
37
37
|
Requires-Dist: oss2>=2.18; extra == "oss"
|
|
38
|
+
Provides-Extra: schema
|
|
39
|
+
Requires-Dist: jsonschema>=4.20; extra == "schema"
|
|
38
40
|
Provides-Extra: monitor
|
|
39
41
|
Requires-Dist: psutil>=5.9; extra == "monitor"
|
|
40
42
|
Provides-Extra: service
|
|
@@ -194,25 +196,51 @@ DDP run on CPU). The strategy planner's code walkthrough lives in
|
|
|
194
196
|
An honest benchmark suite (`python -m benchmarks run`) records every result with
|
|
195
197
|
the host that produced it and reports missing comparators as skipped rows, never
|
|
196
198
|
faked. A measured baseline is committed under
|
|
197
|
-
[`benchmarks/results/`](benchmarks/results/).
|
|
199
|
+
[`benchmarks/results/`](benchmarks/results/).
|
|
200
|
+
|
|
201
|
+
### What it costs to lend your machine
|
|
202
|
+
|
|
203
|
+
Measured on an **Apple M4, 16 GB, macOS, CPU only**. Every sentence below is
|
|
204
|
+
*generated* from the committed result file by
|
|
205
|
+
[`benchmarks/headline.py`](benchmarks/headline.py), never typed here by hand: if
|
|
206
|
+
the README drifts from the JSON,
|
|
207
|
+
`tests/test_headline.py::test_readme_quotes_the_committed_numbers` fails.
|
|
208
|
+
|
|
209
|
+
Waiting for work, the host agent:
|
|
210
|
+
|
|
211
|
+
- uses 0.13% of your computer's memory while waiting for work
|
|
212
|
+
<br><sub>measured on a 16 GB machine; the megabyte figure is fixed, so the percentage is larger on a smaller machine</sub>
|
|
213
|
+
- and under 0.1% of one processor core
|
|
214
|
+
<br><sub>set by --poll-seconds (1.0s); a faster cadence costs more</sub>
|
|
215
|
+
|
|
216
|
+
Running someone else's job on it:
|
|
217
|
+
|
|
218
|
+
- 0 of 24 programs written to break out reached the owner's files, network or disk — each one first verified to succeed when the protection was switched off
|
|
219
|
+
<br><sub>these named attacks did not get out; that is not proof the sandbox is unescapable — see SECURITY.md</sub>
|
|
220
|
+
- the safety measures cost the job's owner nothing we can measure at this workload (between 6% faster and 1% slower)
|
|
221
|
+
<br><sub>negative because --network none skips network setup the bare comparison pays; workload-specific, and it is charged to the job's owner, never to the person lending the machine</sub>
|
|
222
|
+
|
|
223
|
+
### What the runtime does for a job
|
|
198
224
|
|
|
199
225
|
| Scenario | Measured | Read it honestly |
|
|
200
226
|
|---|---|---|
|
|
201
|
-
| Launch overhead | **+0.04 s** | `flash.submit` vs bare `torchrun`, paired. A one-time fixed cost, not per-step. |
|
|
202
|
-
| Checkpoint cost | **−1.2 ms / checkpoint** (p90 6.2 ms) | A few-KB state dict; the median delta is *below* the run-to-run noise floor at this size (it goes slightly negative), so checkpoints are effectively free here — the p90 bounds it. A larger model surfaces a real positive cost. |
|
|
203
227
|
| Auto-resume | **40 steps not recomputed** | The size-*independent* guarantee: resume never re-does work past the last valid checkpoint. |
|
|
204
228
|
| Adoption | **7 lines** | To make a vanilla script framework-ready (difflib vs each project's own docs). |
|
|
205
|
-
| Resilience | **5/5 faults handled · 20/20 integrity under `kill -9`** | Every fault type routed to the right typed recovery; a checkpoint survives a mid-write `SIGKILL` on 20/20 kills (naive `torch.save`: 20/20 corrupted). 16-trial storm, half crash-armed: 16/16 completed, all 8 crashed trials auto-resumed, 0 manual.
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
229
|
+
| Resilience | **5/5 faults handled · 20/20 integrity under `kill -9`** | Every fault type routed to the right typed recovery; a checkpoint survives a mid-write `SIGKILL` on 20/20 kills (naive `torch.save`: 20/20 corrupted). 16-trial storm, half crash-armed: 16/16 completed, all 8 crashed trials auto-resumed, 0 manual. |
|
|
230
|
+
|
|
231
|
+
Two rows that used to sit here — a `+0.04 s` launch cost and a
|
|
232
|
+
`−1.2 ms / checkpoint` saving — have been **removed rather than updated**:
|
|
233
|
+
neither reproduced when re-measured, and a number that no longer reproduces is
|
|
234
|
+
not a smaller number, it is not a result. The figure that scales with a real job
|
|
235
|
+
is `steps_not_recomputed`, not seconds; the seconds saved grow with the compute
|
|
236
|
+
between the last checkpoint and the crash (negligible at smoke size, hours on a
|
|
237
|
+
real job). The resilience counts are exact, not smoke-diluted: they are
|
|
238
|
+
*counted* from real fault injection (`kill -9` in the checkpoint-write window, a
|
|
239
|
+
storm of crash-armed trials, a killed worker).
|
|
240
|
+
|
|
241
|
+
Full statistics — every percentile, every comparator, and the technical units
|
|
242
|
+
the sentences above deliberately avoid — live on
|
|
243
|
+
[the benchmarks page](docs/site/benchmarks.md); full provenance and caveats live
|
|
216
244
|
in the result file.
|
|
217
245
|
|
|
218
246
|
---
|
|
@@ -140,25 +140,51 @@ DDP run on CPU). The strategy planner's code walkthrough lives in
|
|
|
140
140
|
An honest benchmark suite (`python -m benchmarks run`) records every result with
|
|
141
141
|
the host that produced it and reports missing comparators as skipped rows, never
|
|
142
142
|
faked. A measured baseline is committed under
|
|
143
|
-
[`benchmarks/results/`](benchmarks/results/).
|
|
143
|
+
[`benchmarks/results/`](benchmarks/results/).
|
|
144
|
+
|
|
145
|
+
### What it costs to lend your machine
|
|
146
|
+
|
|
147
|
+
Measured on an **Apple M4, 16 GB, macOS, CPU only**. Every sentence below is
|
|
148
|
+
*generated* from the committed result file by
|
|
149
|
+
[`benchmarks/headline.py`](benchmarks/headline.py), never typed here by hand: if
|
|
150
|
+
the README drifts from the JSON,
|
|
151
|
+
`tests/test_headline.py::test_readme_quotes_the_committed_numbers` fails.
|
|
152
|
+
|
|
153
|
+
Waiting for work, the host agent:
|
|
154
|
+
|
|
155
|
+
- uses 0.13% of your computer's memory while waiting for work
|
|
156
|
+
<br><sub>measured on a 16 GB machine; the megabyte figure is fixed, so the percentage is larger on a smaller machine</sub>
|
|
157
|
+
- and under 0.1% of one processor core
|
|
158
|
+
<br><sub>set by --poll-seconds (1.0s); a faster cadence costs more</sub>
|
|
159
|
+
|
|
160
|
+
Running someone else's job on it:
|
|
161
|
+
|
|
162
|
+
- 0 of 24 programs written to break out reached the owner's files, network or disk — each one first verified to succeed when the protection was switched off
|
|
163
|
+
<br><sub>these named attacks did not get out; that is not proof the sandbox is unescapable — see SECURITY.md</sub>
|
|
164
|
+
- the safety measures cost the job's owner nothing we can measure at this workload (between 6% faster and 1% slower)
|
|
165
|
+
<br><sub>negative because --network none skips network setup the bare comparison pays; workload-specific, and it is charged to the job's owner, never to the person lending the machine</sub>
|
|
166
|
+
|
|
167
|
+
### What the runtime does for a job
|
|
144
168
|
|
|
145
169
|
| Scenario | Measured | Read it honestly |
|
|
146
170
|
|---|---|---|
|
|
147
|
-
| Launch overhead | **+0.04 s** | `flash.submit` vs bare `torchrun`, paired. A one-time fixed cost, not per-step. |
|
|
148
|
-
| Checkpoint cost | **−1.2 ms / checkpoint** (p90 6.2 ms) | A few-KB state dict; the median delta is *below* the run-to-run noise floor at this size (it goes slightly negative), so checkpoints are effectively free here — the p90 bounds it. A larger model surfaces a real positive cost. |
|
|
149
171
|
| Auto-resume | **40 steps not recomputed** | The size-*independent* guarantee: resume never re-does work past the last valid checkpoint. |
|
|
150
172
|
| Adoption | **7 lines** | To make a vanilla script framework-ready (difflib vs each project's own docs). |
|
|
151
|
-
| Resilience | **5/5 faults handled · 20/20 integrity under `kill -9`** | Every fault type routed to the right typed recovery; a checkpoint survives a mid-write `SIGKILL` on 20/20 kills (naive `torch.save`: 20/20 corrupted). 16-trial storm, half crash-armed: 16/16 completed, all 8 crashed trials auto-resumed, 0 manual.
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
173
|
+
| Resilience | **5/5 faults handled · 20/20 integrity under `kill -9`** | Every fault type routed to the right typed recovery; a checkpoint survives a mid-write `SIGKILL` on 20/20 kills (naive `torch.save`: 20/20 corrupted). 16-trial storm, half crash-armed: 16/16 completed, all 8 crashed trials auto-resumed, 0 manual. |
|
|
174
|
+
|
|
175
|
+
Two rows that used to sit here — a `+0.04 s` launch cost and a
|
|
176
|
+
`−1.2 ms / checkpoint` saving — have been **removed rather than updated**:
|
|
177
|
+
neither reproduced when re-measured, and a number that no longer reproduces is
|
|
178
|
+
not a smaller number, it is not a result. The figure that scales with a real job
|
|
179
|
+
is `steps_not_recomputed`, not seconds; the seconds saved grow with the compute
|
|
180
|
+
between the last checkpoint and the crash (negligible at smoke size, hours on a
|
|
181
|
+
real job). The resilience counts are exact, not smoke-diluted: they are
|
|
182
|
+
*counted* from real fault injection (`kill -9` in the checkpoint-write window, a
|
|
183
|
+
storm of crash-armed trials, a killed worker).
|
|
184
|
+
|
|
185
|
+
Full statistics — every percentile, every comparator, and the technical units
|
|
186
|
+
the sentences above deliberately avoid — live on
|
|
187
|
+
[the benchmarks page](docs/site/benchmarks.md); full provenance and caveats live
|
|
162
188
|
in the result file.
|
|
163
189
|
|
|
164
190
|
---
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""Chunk arithmetic for elastic work distribution.
|
|
2
|
+
|
|
3
|
+
A pass over the data is cut into `total_chunks` UNIFORM chunks. Uniformity
|
|
4
|
+
is not a convenience: `fedavg_weights` clips any contribution whose norm
|
|
5
|
+
exceeds 3x the round's median, and that cap is only silent on an honest
|
|
6
|
+
round while every contribution covers the same amount of data. Sizing a
|
|
7
|
+
machine's work to its own speed would make a fast machine's honest delta
|
|
8
|
+
look like an attack. So heterogeneity is expressed as how MANY chunks a
|
|
9
|
+
machine completes, never how big one is.
|
|
10
|
+
|
|
11
|
+
Pure stdlib, no I/O: this module is imported both by the driver (which runs
|
|
12
|
+
inside the cloud API and must not grow dependencies) and by the worker
|
|
13
|
+
(which runs inside a --network none container).
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from typing import Iterable
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"ALLOTMENT_HEADROOM",
|
|
22
|
+
"chunk_count",
|
|
23
|
+
"chunk_sequence",
|
|
24
|
+
"coverage",
|
|
25
|
+
"rotation_start",
|
|
26
|
+
"slot_start",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
#: How much more work a task slot is allotted than its share of the round.
|
|
30
|
+
#: Slots are offers, not assignments — most rounds will have slots nobody
|
|
31
|
+
#: claims — so a claimed slot must be able to carry more than its even share
|
|
32
|
+
#: or a round with few machines could never reach coverage. 2.0 is a
|
|
33
|
+
#: starting point to be replaced by measured throughput once round 1 has
|
|
34
|
+
#: run; it is not tuned. [assumption]
|
|
35
|
+
ALLOTMENT_HEADROOM: float = 2.0
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def chunk_count(dataset_size: int, target_chunk_size: int) -> int:
|
|
39
|
+
"""How many chunks one pass over `dataset_size` examples becomes.
|
|
40
|
+
|
|
41
|
+
Rounds UP: a remainder smaller than `target_chunk_size` is still data,
|
|
42
|
+
and dropping it would silently train on less than the pass the caller
|
|
43
|
+
asked for.
|
|
44
|
+
"""
|
|
45
|
+
if dataset_size < 1:
|
|
46
|
+
raise ValueError(f"dataset_size must be >= 1, got {dataset_size}")
|
|
47
|
+
if target_chunk_size < 1:
|
|
48
|
+
raise ValueError(f"target_chunk_size must be >= 1, got {target_chunk_size}")
|
|
49
|
+
return -(-dataset_size // target_chunk_size)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def rotation_start(slot_index: int, total_chunks: int, slots: int) -> int:
|
|
53
|
+
"""Where task slot `slot_index` begins its walk through the chunks.
|
|
54
|
+
|
|
55
|
+
Slots are spread evenly so two machines working at the same speed cover
|
|
56
|
+
disjoint data — they only collide once one has wrapped nearly a full
|
|
57
|
+
pass, by which point the round is close to complete anyway. Overlap is
|
|
58
|
+
wasteful but never wrong: `coverage` counts distinct chunks.
|
|
59
|
+
"""
|
|
60
|
+
if total_chunks < 1:
|
|
61
|
+
raise ValueError(f"total_chunks must be >= 1, got {total_chunks}")
|
|
62
|
+
if slots < 1:
|
|
63
|
+
raise ValueError(f"slots must be >= 1, got {slots}")
|
|
64
|
+
return (slot_index * total_chunks // slots) % total_chunks
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def slot_start(slot_index: int, total_chunks: int, slots: int,
|
|
68
|
+
chunk_offset: int = 0) -> int:
|
|
69
|
+
"""Where slot `slot_index` of a round at `chunk_offset` begins.
|
|
70
|
+
|
|
71
|
+
`rotation_start` spreads the slots of ONE round apart from each other.
|
|
72
|
+
It is a pure function of the slot index, so on its own it hands slot 0
|
|
73
|
+
the identical chunk sequence in every round of a run — and since a round
|
|
74
|
+
closes as soon as it hits its coverage target (which is below a full
|
|
75
|
+
pass whenever `sync_every < 1.0`) and leases are FIFO, the same low slot
|
|
76
|
+
indices are claimed each round and the same prefix of the data is
|
|
77
|
+
trained over and over while the tail is never trained at all. With
|
|
78
|
+
`total_chunks=100, slots=20, sync_every=0.5` and two claimants, every
|
|
79
|
+
round covers chunks 0–54 and reports a healthy `covered=0.55`; chunks
|
|
80
|
+
55–99 are never touched by any round of the run.
|
|
81
|
+
|
|
82
|
+
`chunk_offset` is what advances the whole pattern between rounds: the
|
|
83
|
+
driver sets it to the round's index times the round's coverage TARGET,
|
|
84
|
+
so round r+1 starts where round r's target ended.
|
|
85
|
+
|
|
86
|
+
That advance is MODULAR, not a one-way march. Offsets are
|
|
87
|
+
`(r * target) % total_chunks`, so they cycle: at `total_chunks=100,
|
|
88
|
+
target=50` the sequence is 0, 50, 0, 50, … The distinct offsets are
|
|
89
|
+
spaced `gcd(target, total_chunks)` apart and each round covers `target`
|
|
90
|
+
chunks, which is never smaller than that gap — so one cycle is one full
|
|
91
|
+
pass over the data, and the cycle then repeats for the next epoch.
|
|
92
|
+
Coverage is guaranteed within a pass, not across a whole run; a run of
|
|
93
|
+
many epochs revisits the same offsets by design.
|
|
94
|
+
|
|
95
|
+
This function exists so the offset is applied in exactly one place. Two
|
|
96
|
+
callers need it — `service/modea._expand_fedavg` (which stamps
|
|
97
|
+
`start_chunk` into each slot's payload) and `fedavg_driver` (which
|
|
98
|
+
rebuilds the same allowed set to verify what a slot reports). Drift
|
|
99
|
+
between them is graded, not binary: a one-chunk disagreement trims an
|
|
100
|
+
id off each end of every allotment and the round survives degraded,
|
|
101
|
+
while a large one intersects to nothing, weighs every contribution zero
|
|
102
|
+
and kills the round. `test_a_drifting_expansion_start_is_not_silently
|
|
103
|
+
_credited` exercises the second.
|
|
104
|
+
"""
|
|
105
|
+
return (rotation_start(slot_index, total_chunks, slots)
|
|
106
|
+
+ int(chunk_offset)) % total_chunks
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def chunk_sequence(start: int, total_chunks: int, budget: int) -> list[int]:
|
|
110
|
+
"""The ordered chunk ids a task walks, at most `budget` of them.
|
|
111
|
+
|
|
112
|
+
Cycles: a machine fast enough to finish a whole pass wraps to the start
|
|
113
|
+
rather than sitting idle for the rest of the round. Repeating a chunk
|
|
114
|
+
re-trains on data someone may already have covered, which costs effort
|
|
115
|
+
and not correctness.
|
|
116
|
+
"""
|
|
117
|
+
if total_chunks < 1:
|
|
118
|
+
raise ValueError(f"total_chunks must be >= 1, got {total_chunks}")
|
|
119
|
+
if budget < 0:
|
|
120
|
+
raise ValueError(f"budget must be >= 0, got {budget}")
|
|
121
|
+
return [(start + i) % total_chunks for i in range(budget)]
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def coverage(completed: Iterable[Iterable[int]], total_chunks: int) -> float:
|
|
125
|
+
"""Fraction of the pass covered by the union of every machine's chunks.
|
|
126
|
+
|
|
127
|
+
Union, not sum: two machines that both did chunk 7 covered one chunk of
|
|
128
|
+
data between them. This is the number a round closes on, so counting
|
|
129
|
+
effort instead of data would let a round "complete" having trained on a
|
|
130
|
+
fraction of the pass.
|
|
131
|
+
"""
|
|
132
|
+
if total_chunks < 1:
|
|
133
|
+
raise ValueError(f"total_chunks must be >= 1, got {total_chunks}")
|
|
134
|
+
distinct: set[int] = set()
|
|
135
|
+
for ids in completed:
|
|
136
|
+
distinct.update(ids)
|
|
137
|
+
return len(distinct) / total_chunks
|