flashruntime 0.4.2__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.
Files changed (195) hide show
  1. {flashruntime-0.4.2 → flashruntime-0.5.0}/PKG-INFO +43 -15
  2. {flashruntime-0.4.2 → flashruntime-0.5.0}/README.md +40 -14
  3. flashruntime-0.5.0/flashml_workloads/chunks.py +137 -0
  4. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashml_workloads/fedavg_driver.py +403 -68
  5. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashml_workloads/fedavg_weights.py +96 -43
  6. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashml_workloads/fedavg_worker.py +84 -32
  7. flashruntime-0.5.0/flashruntime/envelope.py +310 -0
  8. flashruntime-0.5.0/flashruntime/images/TAG +1 -0
  9. flashruntime-0.5.0/flashruntime/images/__init__.py +88 -0
  10. flashruntime-0.5.0/flashruntime/images/python-slim.txt +8 -0
  11. flashruntime-0.5.0/flashruntime/images/pytorch-cpu.txt +8 -0
  12. flashruntime-0.5.0/flashruntime/images/pytorch-cuda.txt +8 -0
  13. flashruntime-0.5.0/flashruntime/images/sklearn.txt +11 -0
  14. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/integrations/sklearn.py +15 -3
  15. flashruntime-0.5.0/flashruntime/leases/__init__.py +94 -0
  16. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/protocol/v1alpha1.py +75 -2
  17. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/recipes/command.py +93 -8
  18. flashruntime-0.5.0/flashruntime/recipes/partition.py +142 -0
  19. flashruntime-0.5.0/flashruntime/reduce.py +199 -0
  20. flashruntime-0.5.0/flashruntime/retention.py +224 -0
  21. flashruntime-0.5.0/flashruntime/scheduler/__init__.py +782 -0
  22. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/sdk.py +4 -3
  23. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/service/app.py +44 -5
  24. flashruntime-0.5.0/flashruntime/service/modea.py +1482 -0
  25. flashruntime-0.5.0/flashruntime/validators.py +131 -0
  26. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/viewer/_docs/guides/federated-averaging.html +145 -55
  27. flashruntime-0.5.0/flashruntime/viewer/_docs/search-index.json +1 -0
  28. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/workloads/command.py +32 -3
  29. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime.egg-info/PKG-INFO +43 -15
  30. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime.egg-info/SOURCES.txt +35 -0
  31. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime.egg-info/requires.txt +3 -0
  32. {flashruntime-0.4.2 → flashruntime-0.5.0}/pyproject.toml +21 -2
  33. {flashruntime-0.4.2 → flashruntime-0.5.0}/tests/test_benchmarks.py +710 -0
  34. flashruntime-0.5.0/tests/test_chunks.py +78 -0
  35. flashruntime-0.5.0/tests/test_cost_benchmark.py +1216 -0
  36. flashruntime-0.5.0/tests/test_declared_validators.py +179 -0
  37. flashruntime-0.5.0/tests/test_envelope.py +295 -0
  38. {flashruntime-0.4.2 → flashruntime-0.5.0}/tests/test_fedavg_convergence.py +29 -19
  39. flashruntime-0.5.0/tests/test_fedavg_driver.py +1649 -0
  40. {flashruntime-0.4.2 → flashruntime-0.5.0}/tests/test_fedavg_weights.py +65 -10
  41. {flashruntime-0.4.2 → flashruntime-0.5.0}/tests/test_fedavg_worker.py +99 -20
  42. flashruntime-0.5.0/tests/test_headline.py +118 -0
  43. flashruntime-0.5.0/tests/test_image_manifest_lookup.py +65 -0
  44. {flashruntime-0.4.2 → flashruntime-0.5.0}/tests/test_integrations.py +41 -2
  45. flashruntime-0.5.0/tests/test_job_state_partial.py +167 -0
  46. flashruntime-0.5.0/tests/test_lease_seconds_bound.py +153 -0
  47. flashruntime-0.5.0/tests/test_placement_dependencies.py +258 -0
  48. flashruntime-0.5.0/tests/test_placement_pool.py +170 -0
  49. flashruntime-0.5.0/tests/test_placement_reliability.py +295 -0
  50. flashruntime-0.5.0/tests/test_placement_trusted_argv.py +142 -0
  51. flashruntime-0.5.0/tests/test_protocol_pools.py +68 -0
  52. flashruntime-0.5.0/tests/test_recipe_partition.py +257 -0
  53. flashruntime-0.5.0/tests/test_recipe_pool.py +50 -0
  54. flashruntime-0.5.0/tests/test_recipes_command.py +305 -0
  55. flashruntime-0.5.0/tests/test_reduce.py +223 -0
  56. flashruntime-0.5.0/tests/test_retention.py +391 -0
  57. flashruntime-0.5.0/tests/test_service_artifact_delete.py +159 -0
  58. flashruntime-0.5.0/tests/test_service_fedavg.py +430 -0
  59. flashruntime-0.5.0/tests/test_service_job_result.py +157 -0
  60. flashruntime-0.5.0/tests/test_service_reliability.py +279 -0
  61. flashruntime-0.5.0/tests/test_service_validate_output.py +110 -0
  62. flashruntime-0.5.0/tests/test_submit_envelope.py +210 -0
  63. flashruntime-0.5.0/tests/test_submit_retention.py +119 -0
  64. flashruntime-0.4.2/flashruntime/leases/__init__.py +0 -27
  65. flashruntime-0.4.2/flashruntime/scheduler/__init__.py +0 -334
  66. flashruntime-0.4.2/flashruntime/service/modea.py +0 -839
  67. flashruntime-0.4.2/flashruntime/viewer/_docs/search-index.json +0 -1
  68. flashruntime-0.4.2/tests/test_fedavg_driver.py +0 -933
  69. flashruntime-0.4.2/tests/test_recipes_command.py +0 -154
  70. flashruntime-0.4.2/tests/test_service_fedavg.py +0 -206
  71. {flashruntime-0.4.2 → flashruntime-0.5.0}/LICENSE +0 -0
  72. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashml_workloads/__init__.py +0 -0
  73. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashml_workloads/kmeans_driver.py +0 -0
  74. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashml_workloads/kmeans_shard.py +0 -0
  75. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashml_workloads/sgd_trainer.py +0 -0
  76. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashml_workloads/sharded_kmeans.py +0 -0
  77. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashml_workloads/sklearn_trial.py +0 -0
  78. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/__init__.py +0 -0
  79. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/artifacts/__init__.py +0 -0
  80. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/artifacts/store.py +0 -0
  81. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/backends/__init__.py +0 -0
  82. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/backends/base.py +0 -0
  83. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/backends/kuberay.py +0 -0
  84. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/checkpoint/__init__.py +0 -0
  85. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/checkpoint/catalog.py +0 -0
  86. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/checkpoint/local.py +0 -0
  87. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/checkpoint/store.py +0 -0
  88. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/integrations/__init__.py +0 -0
  89. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/integrations/huggingface.py +0 -0
  90. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/integrations/pytorch.py +0 -0
  91. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/launchers/__init__.py +0 -0
  92. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/launchers/local.py +0 -0
  93. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/leases/manager.py +0 -0
  94. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/leases/sqlite_store.py +0 -0
  95. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/leases/store.py +0 -0
  96. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/monitor/__init__.py +0 -0
  97. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/monitor/sampler.py +0 -0
  98. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/planner/__init__.py +0 -0
  99. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/planner/candidates.py +0 -0
  100. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/planner/catalog.py +0 -0
  101. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/planner/comm.py +0 -0
  102. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/planner/explain.py +0 -0
  103. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/planner/memory.py +0 -0
  104. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/planner/resolve.py +0 -0
  105. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/planner/selector.py +0 -0
  106. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/planner/timecost.py +0 -0
  107. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/profiling/__init__.py +0 -0
  108. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/protocol/__init__.py +0 -0
  109. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/protocol/plan_v1alpha1.py +0 -0
  110. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/providers/__init__.py +0 -0
  111. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/py.typed +0 -0
  112. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/recipes/__init__.py +0 -0
  113. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/recovery/__init__.py +0 -0
  114. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/recovery/policy.py +0 -0
  115. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/recovery/signals.py +0 -0
  116. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/recovery/taxonomy.py +0 -0
  117. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/service/__init__.py +0 -0
  118. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/service/auth.py +0 -0
  119. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/service/checkpoints.py +0 -0
  120. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/service/cli.py +0 -0
  121. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/service/dashboard.py +0 -0
  122. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/service/ledger.py +0 -0
  123. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/strategies/__init__.py +0 -0
  124. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/strategies/command.py +0 -0
  125. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/torch/__init__.py +0 -0
  126. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/viewer/__init__.py +0 -0
  127. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/viewer/_docs/benchmarks.html +0 -0
  128. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/viewer/_docs/concepts/architecture.html +0 -0
  129. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/viewer/_docs/get-started.html +0 -0
  130. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/viewer/_docs/guides/huggingface.html +0 -0
  131. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/viewer/_docs/guides/jobspec-and-isolation.html +0 -0
  132. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/viewer/_docs/guides/pytorch.html +0 -0
  133. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/viewer/_docs/guides/sklearn.html +0 -0
  134. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/viewer/_docs/index.html +0 -0
  135. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/viewer/_docs/reference/cli.html +0 -0
  136. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/viewer/_docs/reference/integrations.html +0 -0
  137. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/viewer/_docs/reference/sdk.html +0 -0
  138. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/viewer/_docs/reference/torch-helper.html +0 -0
  139. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/viewer/_docs/tutorials/convnet.html +0 -0
  140. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/viewer/_docs/tutorials/fault-tolerance.html +0 -0
  141. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/viewer/_docs/tutorials/sklearn-sweeps.html +0 -0
  142. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/viewer/flowmap.py +0 -0
  143. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/viewer/page.py +0 -0
  144. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/viewer/server.py +0 -0
  145. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/viewer/state.py +0 -0
  146. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime/workloads/__init__.py +0 -0
  147. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime.egg-info/dependency_links.txt +0 -0
  148. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime.egg-info/entry_points.txt +0 -0
  149. {flashruntime-0.4.2 → flashruntime-0.5.0}/flashruntime.egg-info/top_level.txt +0 -0
  150. {flashruntime-0.4.2 → flashruntime-0.5.0}/setup.cfg +0 -0
  151. {flashruntime-0.4.2 → flashruntime-0.5.0}/tests/test_auto_recovery.py +0 -0
  152. {flashruntime-0.4.2 → flashruntime-0.5.0}/tests/test_checkpoint.py +0 -0
  153. {flashruntime-0.4.2 → flashruntime-0.5.0}/tests/test_checkpoint_local.py +0 -0
  154. {flashruntime-0.4.2 → flashruntime-0.5.0}/tests/test_cli_submit.py +0 -0
  155. {flashruntime-0.4.2 → flashruntime-0.5.0}/tests/test_documentation.py +0 -0
  156. {flashruntime-0.4.2 → flashruntime-0.5.0}/tests/test_examples_e2e.py +0 -0
  157. {flashruntime-0.4.2 → flashruntime-0.5.0}/tests/test_gpu_e2e.py +0 -0
  158. {flashruntime-0.4.2 → flashruntime-0.5.0}/tests/test_interfaces.py +0 -0
  159. {flashruntime-0.4.2 → flashruntime-0.5.0}/tests/test_kmeans_leases.py +0 -0
  160. {flashruntime-0.4.2 → flashruntime-0.5.0}/tests/test_kmeans_workload.py +0 -0
  161. {flashruntime-0.4.2 → flashruntime-0.5.0}/tests/test_kuberay_backend.py +0 -0
  162. {flashruntime-0.4.2 → flashruntime-0.5.0}/tests/test_launcher_local.py +0 -0
  163. {flashruntime-0.4.2 → flashruntime-0.5.0}/tests/test_leases.py +0 -0
  164. {flashruntime-0.4.2 → flashruntime-0.5.0}/tests/test_leases_scope.py +0 -0
  165. {flashruntime-0.4.2 → flashruntime-0.5.0}/tests/test_leases_sqlite.py +0 -0
  166. {flashruntime-0.4.2 → flashruntime-0.5.0}/tests/test_monitor_sampler.py +0 -0
  167. {flashruntime-0.4.2 → flashruntime-0.5.0}/tests/test_placement_exclusion.py +0 -0
  168. {flashruntime-0.4.2 → flashruntime-0.5.0}/tests/test_placement_gpu.py +0 -0
  169. {flashruntime-0.4.2 → flashruntime-0.5.0}/tests/test_placement_local_data.py +0 -0
  170. {flashruntime-0.4.2 → flashruntime-0.5.0}/tests/test_planner.py +0 -0
  171. {flashruntime-0.4.2 → flashruntime-0.5.0}/tests/test_protocol_evidence.py +0 -0
  172. {flashruntime-0.4.2 → flashruntime-0.5.0}/tests/test_protocol_gpu.py +0 -0
  173. {flashruntime-0.4.2 → flashruntime-0.5.0}/tests/test_protocol_local_datasets.py +0 -0
  174. {flashruntime-0.4.2 → flashruntime-0.5.0}/tests/test_protocol_v1alpha1.py +0 -0
  175. {flashruntime-0.4.2 → flashruntime-0.5.0}/tests/test_recipe_gpu.py +0 -0
  176. {flashruntime-0.4.2 → flashruntime-0.5.0}/tests/test_recipe_local_inputs.py +0 -0
  177. {flashruntime-0.4.2 → flashruntime-0.5.0}/tests/test_recovery.py +0 -0
  178. {flashruntime-0.4.2 → flashruntime-0.5.0}/tests/test_run_json.py +0 -0
  179. {flashruntime-0.4.2 → flashruntime-0.5.0}/tests/test_scheduler_isolation.py +0 -0
  180. {flashruntime-0.4.2 → flashruntime-0.5.0}/tests/test_sdk_submit.py +0 -0
  181. {flashruntime-0.4.2 → flashruntime-0.5.0}/tests/test_service_auth.py +0 -0
  182. {flashruntime-0.4.2 → flashruntime-0.5.0}/tests/test_service_auth_startup.py +0 -0
  183. {flashruntime-0.4.2 → flashruntime-0.5.0}/tests/test_service_checkpoints.py +0 -0
  184. {flashruntime-0.4.2 → flashruntime-0.5.0}/tests/test_service_command_recipe.py +0 -0
  185. {flashruntime-0.4.2 → flashruntime-0.5.0}/tests/test_service_delegation.py +0 -0
  186. {flashruntime-0.4.2 → flashruntime-0.5.0}/tests/test_service_modea.py +0 -0
  187. {flashruntime-0.4.2 → flashruntime-0.5.0}/tests/test_service_write_scope.py +0 -0
  188. {flashruntime-0.4.2 → flashruntime-0.5.0}/tests/test_sgd_trainer.py +0 -0
  189. {flashruntime-0.4.2 → flashruntime-0.5.0}/tests/test_strategy_command.py +0 -0
  190. {flashruntime-0.4.2 → flashruntime-0.5.0}/tests/test_torch_helper.py +0 -0
  191. {flashruntime-0.4.2 → flashruntime-0.5.0}/tests/test_viewer_flowmap.py +0 -0
  192. {flashruntime-0.4.2 → flashruntime-0.5.0}/tests/test_viewer_page.py +0 -0
  193. {flashruntime-0.4.2 → flashruntime-0.5.0}/tests/test_viewer_server.py +0 -0
  194. {flashruntime-0.4.2 → flashruntime-0.5.0}/tests/test_viewer_state.py +0 -0
  195. {flashruntime-0.4.2 → 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.4.2
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/). On **Apple M4 (CPU)**:
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. Dead-worker MTTD **3.05 s**, MTTR **~3.5 ms**. |
206
-
207
- The *timing* rows are smoke-scale on one host: the models are tiny, so the
208
- *wall-clock* saved by recovery is at the noise floor here. The figure that
209
- scales is `steps_not_recomputed`, not seconds the seconds-saved grows with the
210
- compute between the last checkpoint and the crash (negligible at smoke size,
211
- hours on a real job). The resilience counts are exact, not smoke-diluted: they
212
- are *counted* from real fault injection (`kill -9` in the checkpoint-write
213
- window, a storm of crash-armed trials, a killed lease worker), and the full
214
- Resilience table with per-scenario method lives in
215
- [the benchmarks page](docs/site/benchmarks.md). Full provenance and caveats live
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/). On **Apple M4 (CPU)**:
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. Dead-worker MTTD **3.05 s**, MTTR **~3.5 ms**. |
152
-
153
- The *timing* rows are smoke-scale on one host: the models are tiny, so the
154
- *wall-clock* saved by recovery is at the noise floor here. The figure that
155
- scales is `steps_not_recomputed`, not seconds the seconds-saved grows with the
156
- compute between the last checkpoint and the crash (negligible at smoke size,
157
- hours on a real job). The resilience counts are exact, not smoke-diluted: they
158
- are *counted* from real fault injection (`kill -9` in the checkpoint-write
159
- window, a storm of crash-armed trials, a killed lease worker), and the full
160
- Resilience table with per-scenario method lives in
161
- [the benchmarks page](docs/site/benchmarks.md). Full provenance and caveats live
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