freesolo-flash-dev 1.0.76__tar.gz → 1.0.78__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 (394) hide show
  1. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/Dockerfile.worker +1 -1
  2. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/PKG-INFO +1 -1
  3. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/catalog.py +1 -0
  4. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/cli/TRAINING.md +5 -5
  5. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/cli/__init__.py +1 -1
  6. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/cli/envpush.py +145 -76
  7. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/cost/analytical.py +113 -67
  8. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/cost/spec.py +3 -3
  9. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/engine/vram.py +3 -3
  10. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/engine/worker/backend_common.py +44 -17
  11. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/providers/allocator.py +44 -11
  12. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/providers/base.py +8 -4
  13. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/providers/lambdalabs/__init__.py +29 -0
  14. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/providers/lambdalabs/gpus.py +28 -3
  15. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/providers/lambdalabs/pricing.py +2 -2
  16. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/runner/__init__.py +15 -9
  17. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/runner/lifecycle.py +121 -40
  18. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/schema/__init__.py +7 -2
  19. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/pyproject.toml +2 -2
  20. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_allocator.py +2 -0
  21. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_backend_common.py +95 -7
  22. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_cli_gpus_flag.py +17 -7
  23. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_cost_estimate.py +123 -119
  24. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_disk_gb.py +6 -1
  25. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_env_push.py +127 -1
  26. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_jobs.py +83 -0
  27. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_lambda_runner.py +2 -0
  28. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_multi_gpu_gate.py +241 -56
  29. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_opd.py +13 -0
  30. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_provider_routing.py +13 -6
  31. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_resume_on_retry.py +1 -1
  32. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_runmgmt.py +12 -6
  33. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/uv.lock +1 -1
  34. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/.dockerignore +0 -0
  35. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/.env.example +0 -0
  36. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  37. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  38. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  39. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  40. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/.github/dependabot.yml +0 -0
  41. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/.github/workflows/auto-rebake.yml +0 -0
  42. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/.github/workflows/bake-kernel-cache.yml +0 -0
  43. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/.github/workflows/ci.yml +0 -0
  44. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/.github/workflows/main-source-guard.yml +0 -0
  45. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/.github/workflows/notify-tests-repo.yml +0 -0
  46. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/.github/workflows/publish-dev.yml +0 -0
  47. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/.github/workflows/publish-image.yml +0 -0
  48. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/.github/workflows/publish.yml +0 -0
  49. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/.github/workflows/version-parity.yml +0 -0
  50. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/.github/workflows/worker-image.yml +0 -0
  51. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/.gitignore +0 -0
  52. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/CODE_OF_CONDUCT.md +0 -0
  53. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/CONTRIBUTING.md +0 -0
  54. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/Dockerfile +0 -0
  55. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/LICENSE +0 -0
  56. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/NOTICE +0 -0
  57. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/README.md +0 -0
  58. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/SECURITY.md +0 -0
  59. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/SELF_HOSTING.md +0 -0
  60. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/build/kernel_cache/.gitignore +0 -0
  61. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/build/kernel_cache/.keep +0 -0
  62. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/docker/Dockerfile.kernelcache +0 -0
  63. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/docker/Dockerfile.kernelcache.relayer +0 -0
  64. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/docker/bake_kernel_cache.py +0 -0
  65. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/docker/bake_pod_entry.py +0 -0
  66. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/docker/kernel_fingerprint.py +0 -0
  67. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/docker/make_rp_handler.py +0 -0
  68. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/__init__.py +0 -0
  69. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/_channel.py +0 -0
  70. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/_fileio.py +0 -0
  71. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/_logging.py +0 -0
  72. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/_update_check.py +0 -0
  73. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/adapter_artifacts.py +0 -0
  74. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/cli/__main__.py +0 -0
  75. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/cli/_tty.py +0 -0
  76. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/cli/commands.py +0 -0
  77. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/cli/env_eval.py +0 -0
  78. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/cli/env_setup.py +0 -0
  79. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/cli/env_test.py +0 -0
  80. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/cli/render.py +0 -0
  81. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/cli/traces.py +0 -0
  82. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/cli/training_doc.py +0 -0
  83. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/client/__init__.py +0 -0
  84. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/client/config.py +0 -0
  85. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/client/http.py +0 -0
  86. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/client/runtime_secrets.py +0 -0
  87. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/client/specs.py +0 -0
  88. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/cost/__init__.py +0 -0
  89. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/cost/facts.py +0 -0
  90. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/cost/types.py +0 -0
  91. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/diagnostics.py +0 -0
  92. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/engine/__init__.py +0 -0
  93. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/engine/accounting.py +0 -0
  94. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/engine/multiturn_reward_scoring.py +0 -0
  95. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/engine/recipe.py +0 -0
  96. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/engine/reward_profile.py +0 -0
  97. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/engine/steps.py +0 -0
  98. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/engine/structured_outputs.py +0 -0
  99. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/engine/worker/__init__.py +0 -0
  100. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/engine/worker/__main__.py +0 -0
  101. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/engine/worker/_pkg.py +0 -0
  102. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/engine/worker/adapter.py +0 -0
  103. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/engine/worker/decoding.py +0 -0
  104. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/engine/worker/finalize.py +0 -0
  105. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/engine/worker/grpo.py +0 -0
  106. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/engine/worker/grpo_multiturn.py +0 -0
  107. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/engine/worker/grpo_plugin.py +0 -0
  108. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/engine/worker/heartbeat.py +0 -0
  109. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/engine/worker/hf.py +0 -0
  110. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/engine/worker/kernel_warmup.py +0 -0
  111. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/engine/worker/lora.py +0 -0
  112. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/engine/worker/multiturn_glue.py +0 -0
  113. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/engine/worker/opd.py +0 -0
  114. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/engine/worker/opd_gkd.py +0 -0
  115. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/engine/worker/opd_multiturn.py +0 -0
  116. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/engine/worker/opd_plugin.py +0 -0
  117. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/engine/worker/opd_structured.py +0 -0
  118. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/engine/worker/opd_train.py +0 -0
  119. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/engine/worker/packing.py +0 -0
  120. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/engine/worker/perf/__init__.py +0 -0
  121. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/engine/worker/perf/attn.py +0 -0
  122. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/engine/worker/perf/diagnostics.py +0 -0
  123. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/engine/worker/perf/lifecycle.py +0 -0
  124. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/engine/worker/perf/liger.py +0 -0
  125. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/engine/worker/perf/loraplus.py +0 -0
  126. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/engine/worker/perf/memory.py +0 -0
  127. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/engine/worker/rl.py +0 -0
  128. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/engine/worker/rl_train.py +0 -0
  129. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/engine/worker/rng.py +0 -0
  130. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/engine/worker/rollout_samples.py +0 -0
  131. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/engine/worker/sft.py +0 -0
  132. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/engine/worker/sft_train.py +0 -0
  133. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/engine/worker/teacher.py +0 -0
  134. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/engine/worker/tokenizer_align.py +0 -0
  135. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/engine/worker/wandb_log.py +0 -0
  136. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/engine/worker_entrypoint.py +0 -0
  137. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/envs/__init__.py +0 -0
  138. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/envs/adapter.py +0 -0
  139. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/envs/archive.py +0 -0
  140. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/envs/archive_policy.py +0 -0
  141. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/envs/base.py +0 -0
  142. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/envs/evaluations.py +0 -0
  143. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/envs/loader.py +0 -0
  144. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/envs/pull.py +0 -0
  145. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/envs/registry.py +0 -0
  146. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/lora_rank.py +0 -0
  147. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/multimodal.py +0 -0
  148. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/opd_retry_contract.py +0 -0
  149. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/opd_validation.py +0 -0
  150. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/providers/__init__.py +0 -0
  151. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/providers/_auth.py +0 -0
  152. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/providers/_deadline.py +0 -0
  153. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/providers/_hf_artifacts.py +0 -0
  154. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/providers/_hf_retry.py +0 -0
  155. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/providers/_http.py +0 -0
  156. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/providers/_instance.py +0 -0
  157. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/providers/_instance_bootstrap.py +0 -0
  158. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/providers/_instance_poll.py +0 -0
  159. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/providers/_instance_provider.py +0 -0
  160. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/providers/_poll.py +0 -0
  161. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/providers/_worker.py +0 -0
  162. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/providers/lambdalabs/api.py +0 -0
  163. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/providers/lambdalabs/auth.py +0 -0
  164. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/providers/lambdalabs/jobs/__init__.py +0 -0
  165. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/providers/lambdalabs/jobs/builders.py +0 -0
  166. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/providers/lambdalabs/preflight.py +0 -0
  167. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/providers/preflight.py +0 -0
  168. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/providers/realized.py +0 -0
  169. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/providers/runpod/__init__.py +0 -0
  170. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/providers/runpod/api.py +0 -0
  171. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/providers/runpod/auth.py +0 -0
  172. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/providers/runpod/cost.py +0 -0
  173. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/providers/runpod/gpus.py +0 -0
  174. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/providers/runpod/jobs.py +0 -0
  175. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/providers/runpod/keys.py +0 -0
  176. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/providers/runpod/preflight.py +0 -0
  177. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/providers/runpod/pricing.py +0 -0
  178. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/providers/runpod/slots.py +0 -0
  179. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/providers/runpod/train/__init__.py +0 -0
  180. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/providers/runpod/train/endpoints.py +0 -0
  181. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/providers/vast/__init__.py +0 -0
  182. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/providers/vast/api.py +0 -0
  183. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/providers/vast/auth.py +0 -0
  184. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/providers/vast/jobs/__init__.py +0 -0
  185. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/providers/vast/jobs/builders.py +0 -0
  186. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/providers/vast/preflight.py +0 -0
  187. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/providers/vast/pricing.py +0 -0
  188. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/providers/weight_cache_preload.py +0 -0
  189. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/py.typed +0 -0
  190. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/runner/checkpoints.py +0 -0
  191. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/runner/deploy.py +0 -0
  192. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/runner/verified_revisions.py +0 -0
  193. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/schema/fields.py +0 -0
  194. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/serve/__init__.py +0 -0
  195. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/serve/deploy.py +0 -0
  196. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/serve/export.py +0 -0
  197. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/serve/preflight.py +0 -0
  198. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/serve/pricing.py +0 -0
  199. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/serve/urls.py +0 -0
  200. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/server/__init__.py +0 -0
  201. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/server/__main__.py +0 -0
  202. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/server/_deps.py +0 -0
  203. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/server/_internal_client.py +0 -0
  204. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/server/_locks.py +0 -0
  205. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/server/_runtime.py +0 -0
  206. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/server/app.py +0 -0
  207. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/server/auth.py +0 -0
  208. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/server/billing.py +0 -0
  209. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/server/billing_retry.py +0 -0
  210. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/server/checkpoints.py +0 -0
  211. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/server/db.py +0 -0
  212. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/server/environment_registry.py +0 -0
  213. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/server/envs.py +0 -0
  214. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/server/projects.py +0 -0
  215. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/server/reconcile.py +0 -0
  216. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/server/repo_cleanup.py +0 -0
  217. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/server/routes/__init__.py +0 -0
  218. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/server/routes/envs.py +0 -0
  219. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/server/routes/meta.py +0 -0
  220. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/server/routes/runs.py +0 -0
  221. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/server/routes/serving.py +0 -0
  222. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/server/run_registry.py +0 -0
  223. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/spec.py +0 -0
  224. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/flash/thinking.py +0 -0
  225. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/infisical-entrypoint.sh +0 -0
  226. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/scripts/build_dev_dist.py +0 -0
  227. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/scripts/scrub_history.sh +0 -0
  228. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/__init__.py +0 -0
  229. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/_helpers/__init__.py +0 -0
  230. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/_helpers/runner.py +0 -0
  231. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/_helpers/specs.py +0 -0
  232. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/_helpers/vast.py +0 -0
  233. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/conftest.py +0 -0
  234. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/fixtures/math_eval.jsonl +0 -0
  235. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/fixtures/math_train.jsonl +0 -0
  236. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/live/__init__.py +0 -0
  237. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/live/conftest.py +0 -0
  238. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/live/test_lambda_live.py +0 -0
  239. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/live/test_runpod_live.py +0 -0
  240. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/live/test_vast_live.py +0 -0
  241. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_accounting_coverage.py +0 -0
  242. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_agent_flash_cli_contract.py +0 -0
  243. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_algorithms.py +0 -0
  244. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_async_uploads.py +0 -0
  245. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_backend_jobspec_contract.py +0 -0
  246. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_base_model_provenance.py +0 -0
  247. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_billing_retry.py +0 -0
  248. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_cancel_remote.py +0 -0
  249. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_catalog_consistency.py +0 -0
  250. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_charge_pricing.py +0 -0
  251. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_checkpoints.py +0 -0
  252. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_cli_commands.py +0 -0
  253. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_cli_commands_coverage.py +0 -0
  254. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_cli_credential_shadowing.py +0 -0
  255. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_cli_errors.py +0 -0
  256. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_cli_estimate.py +0 -0
  257. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_cli_help.py +0 -0
  258. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_cli_main_coverage.py +0 -0
  259. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_cli_managed.py +0 -0
  260. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_cli_render_coverage.py +0 -0
  261. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_cli_render_theme.py +0 -0
  262. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_cli_traces.py +0 -0
  263. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_cli_wandb_warning.py +0 -0
  264. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_client.py +0 -0
  265. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_client_server_integration.py +0 -0
  266. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_client_stream_reads.py +0 -0
  267. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_config_overrides.py +0 -0
  268. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_controlled_experiment_repairs.py +0 -0
  269. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_cost_analytical.py +0 -0
  270. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_cost_equation.py +0 -0
  271. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_cost_hardware.py +0 -0
  272. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_cost_models.py +0 -0
  273. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_cost_rewards.py +0 -0
  274. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_deploy_latency.py +0 -0
  275. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_dev_channel.py +0 -0
  276. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_endpoint_name.py +0 -0
  277. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_env_cache_evict.py +0 -0
  278. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_env_delete.py +0 -0
  279. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_env_download.py +0 -0
  280. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_env_download_api.py +0 -0
  281. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_env_evaluations.py +0 -0
  282. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_env_no_install.py +0 -0
  283. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_env_publish.py +0 -0
  284. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_env_pull.py +0 -0
  285. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_env_pull_coverage.py +0 -0
  286. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_env_pull_loader_coverage.py +0 -0
  287. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_env_pull_managed_control_plane.py +0 -0
  288. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_env_rate_limit_resolve.py +0 -0
  289. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_env_ref_pinning.py +0 -0
  290. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_env_test.py +0 -0
  291. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_envs_coverage.py +0 -0
  292. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_export.py +0 -0
  293. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_flash_mvp.py +0 -0
  294. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_flash_worker.py +0 -0
  295. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_flashinfer_cache_dirs.py +0 -0
  296. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_github_urlopen_retry.py +0 -0
  297. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_gpu_type_single_field.py +0 -0
  298. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_gpus.py +0 -0
  299. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_grpo_mask_aware.py +0 -0
  300. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_grpo_params.py +0 -0
  301. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_grpo_resident_sizing.py +0 -0
  302. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_health_liveness.py +0 -0
  303. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_hf_retry.py +0 -0
  304. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_idle_endpoint_reaper.py +0 -0
  305. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_instance_bootstrap_coverage.py +0 -0
  306. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_internal_client.py +0 -0
  307. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_kernel_cache.py +0 -0
  308. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_kernel_fingerprint.py +0 -0
  309. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_kv_util.py +0 -0
  310. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_lambda_api_coverage.py +0 -0
  311. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_logging.py +0 -0
  312. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_login_perms.py +0 -0
  313. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_lora_rank_coverage.py +0 -0
  314. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_lora_rank_preflight.py +0 -0
  315. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_managed_config_fields.py +0 -0
  316. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_managed_hf_repo.py +0 -0
  317. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_metrics_schema_agent_contract.py +0 -0
  318. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_mig_guard.py +0 -0
  319. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_multimodal_input_grads.py +0 -0
  320. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_multimodal_training.py +0 -0
  321. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_named_reward_metrics.py +0 -0
  322. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_oom_escalate_gpu.py +0 -0
  323. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_opd_resume_safety.py +0 -0
  324. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_opd_train.py +0 -0
  325. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_open_model_policy.py +0 -0
  326. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_orchestrator_flash.py +0 -0
  327. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_packing.py +0 -0
  328. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_packing_coverage.py +0 -0
  329. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_poll_helpers.py +0 -0
  330. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_preflight.py +0 -0
  331. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_projects.py +0 -0
  332. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_provider_preflight_coverage.py +0 -0
  333. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_provider_teardown_robustness.py +0 -0
  334. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_providers_symmetry.py +0 -0
  335. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_realized_cost.py +0 -0
  336. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_repo_cleanup.py +0 -0
  337. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_resolve_params_b.py +0 -0
  338. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_reward_profile.py +0 -0
  339. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_rl_train.py +0 -0
  340. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_rollout_samples.py +0 -0
  341. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_runpod_api_delete.py +0 -0
  342. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_runpod_key_fingerprint.py +0 -0
  343. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_runpod_key_waterfall.py +0 -0
  344. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_runpod_slots.py +0 -0
  345. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_serve.py +0 -0
  346. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_serve_reasoning_content.py +0 -0
  347. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_server_api.py +0 -0
  348. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_server_app_coverage.py +0 -0
  349. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_server_auth_singleflight.py +0 -0
  350. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_server_billing.py +0 -0
  351. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_server_checkpoints_coverage.py +0 -0
  352. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_server_db.py +0 -0
  353. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_server_envs_coverage.py +0 -0
  354. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_server_locks_coverage.py +0 -0
  355. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_server_standalone.py +0 -0
  356. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_serving_context_preflight.py +0 -0
  357. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_serving_contract.py +0 -0
  358. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_serving_pricing_coverage.py +0 -0
  359. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_serving_schema_coverage.py +0 -0
  360. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_sft_chunked_nll.py +0 -0
  361. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_sft_example_selection.py +0 -0
  362. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_sft_gc_off.py +0 -0
  363. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_sft_max_context.py +0 -0
  364. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_sft_train.py +0 -0
  365. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_spec_and_validation.py +0 -0
  366. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_step_metrics.py +0 -0
  367. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_structured_outputs.py +0 -0
  368. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_thinking_config.py +0 -0
  369. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_train_context_preflight.py +0 -0
  370. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_training_controls.py +0 -0
  371. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_update_check.py +0 -0
  372. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_vast_api.py +0 -0
  373. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_vast_api_coverage.py +0 -0
  374. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_vast_offers.py +0 -0
  375. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_vast_provider_coverage.py +0 -0
  376. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_vast_runner.py +0 -0
  377. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_verified_revisions.py +0 -0
  378. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_verifiers.py +0 -0
  379. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_version.py +0 -0
  380. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_vl_warmstart_adapter_keys.py +0 -0
  381. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_vram_architecture.py +0 -0
  382. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_wandb_link.py +0 -0
  383. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_wandb_log_coverage.py +0 -0
  384. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_wandb_naming.py +0 -0
  385. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_warmstart_adapter_download.py +0 -0
  386. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_warmstart_cross_repo.py +0 -0
  387. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_warmstart_error_attribution.py +0 -0
  388. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_weight_cache.py +0 -0
  389. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_worker_dryrun.py +0 -0
  390. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_worker_hardexit.py +0 -0
  391. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_worker_image.py +0 -0
  392. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_worker_init_heartbeat.py +0 -0
  393. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_worker_stack.py +0 -0
  394. {freesolo_flash_dev-1.0.76 → freesolo_flash_dev-1.0.78}/tests/test_worker_thinking.py +0 -0
@@ -268,7 +268,7 @@ print('worker stack OK | torch', torch.__version__, '| cudnn', cudnn, '| triton'
268
268
  # flash's, so a job that sets WANDB_API_KEY loses its curves and run link unless wandb is importable
269
269
  # in here; resolve_verl_loggers() degrades to console when it is not, which is the right behaviour
270
270
  # for a foreign FLASH_VERL_PYTHON but silently drops W&B on the default image (codex[bot]).
271
- ARG VERL_SPEC=verl[vllm]@git+https://github.com/freesolo-co/verl@b7492fa3b7ab843294d06dbf754e887950f559c7
271
+ ARG VERL_SPEC=verl[vllm]@git+https://github.com/freesolo-co/verl@1bea7d6825bbb9d2164e86e379b3680e7c53bb8a
272
272
  ENV FLASH_VERL_PYTHON=/opt/verl-venv/bin/python
273
273
  RUN printf '%s\n' "numpy==2.2.6" "xgrammar==0.1.25" "vllm==0.19.1" > /tmp/verl-overrides.txt \
274
274
  && uv venv --seed /opt/verl-venv \
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: freesolo-flash-dev
3
- Version: 1.0.76
3
+ Version: 1.0.78
4
4
  Summary: Flash — managed LoRA post-training (SFT/GRPO/OPD) for Freesolo environments, driven by the `flash` CLI
5
5
  Project-URL: Homepage, https://github.com/freesolo-co/flash
6
6
  Project-URL: Repository, https://github.com/freesolo-co/flash
@@ -369,6 +369,7 @@ MODELS: dict[str, ModelInfo] = {
369
369
  ),
370
370
  algos=ALGORITHMS,
371
371
  min_vram_gb=48,
372
+ min_disk_gb=120,
372
373
  # NOT QLoRA: peft bnb merge during GRPO rollout diverges trainer precision -> TRL ratio collapses to 0.
373
374
  grpo_min_vram_gb=80,
374
375
  quant="bf16",
@@ -1286,13 +1286,13 @@ gpu.count=4`, and the same 1..8 bound rejects a bad value.
1286
1286
  most cards it may use, and it stops at the first count that fits: a class that fits the run on one
1287
1287
  card is allocated as one card, and a class that needs sharding gets the _smallest_ fitting
1288
1288
  combination, not the count you asked for. `--gpus 4` on a 9B **SFT** run pinned to a 24 GB class
1289
- allocates 2. Combinations are also capped at 4, so 5-8 only lower the count that would otherwise be
1290
- chosen.
1289
+ allocates 2. Eight cards is the public and allocatable maximum; ceilings between powers of two round
1290
+ down to the next rentable count.
1291
1291
 
1292
1292
  That example is SFT-specific on purpose. The fit test is per algorithm, and the same 9B pinned to
1293
- the same 24 GB class does not allocate at all under GRPO or OPD — it raises `UnsupportedGpuError:
1294
- ... cannot fit this run even as a 4-card combination`, because rollout memory pushes it past what
1295
- four such cards hold. Raising `--gpus` cannot rescue a pin the algorithm never fits on.
1293
+ the same 24 GB class may need a wider combination under GRPO or OPD because rollout memory raises
1294
+ the whole-run floor. Raising `--gpus` still cannot rescue a pin the algorithm does not fit on even
1295
+ as an 8-card combination.
1296
1296
 
1297
1297
  **There is no exact-count mechanism.** Pinning a small `[gpu] type` raises the floor above one card
1298
1298
  but still does not pin n — it only moves which combination is smallest. To see what a submit
@@ -577,7 +577,7 @@ def _build_parser() -> argparse.ArgumentParser:
577
577
  help=(
578
578
  "most cards to run the job on; sets [gpu] count (1-8). a ceiling, not an exact "
579
579
  "count: allocation still picks one card when one fits the run alone, and only "
580
- "rentable counts (1, 2, 4) are ever provisioned"
580
+ "rentable counts (1, 2, 4, 8) are ever provisioned"
581
581
  ),
582
582
  )
583
583
  train.add_argument("--dry-run", action="store_true")
@@ -605,8 +605,15 @@ def _dynamic_import_name(node, callees: frozenset[str]) -> str | None:
605
605
  return first.value.split(".", 1)[0] or None
606
606
 
607
607
 
608
- def _imported_module_names(tree) -> set[str]:
609
- """Top-level names a parsed module imports absolutely, statement or literal call."""
608
+ def _imported_module_names(tree, *, relative_names_are_siblings: bool = True) -> set[str]:
609
+ """Top-level sibling names a parsed module imports, by statement or literal call.
610
+
611
+ Every name returned is resolved against the env root by the caller, so a relative import
612
+ only belongs here when the parsed file sits AT that root. Pass
613
+ `relative_names_are_siblings=False` for a file nested deeper: `from . import config` inside
614
+ `pkg/__init__.py` names `pkg/config.py`, which the package walk already ships, and reading it
615
+ as a root-level name published an unrelated top-level `config.py` instead (codex[bot]).
616
+ """
610
617
  import ast
611
618
 
612
619
  names: set[str] = set()
@@ -614,8 +621,22 @@ def _imported_module_names(tree) -> set[str]:
614
621
  for node in ast.walk(tree):
615
622
  if isinstance(node, ast.Import):
616
623
  names.update(alias.name.split(".", 1)[0] for alias in node.names)
617
- elif isinstance(node, ast.ImportFrom) and node.level == 0 and node.module:
618
- names.add(node.module.split(".", 1)[0])
624
+ elif isinstance(node, ast.ImportFrom):
625
+ if node.level == 0:
626
+ if node.module:
627
+ names.add(node.module.split(".", 1)[0])
628
+ elif node.level == 1 and relative_names_are_siblings:
629
+ # `from . import config` and `from .utils import load` name siblings of this file
630
+ # just as the absolute spellings do. an entrypoint written to work both as a
631
+ # package member and as a loose module puts the relative form first and falls back
632
+ # to the absolute one, so reading only absolute imports left the helper unpackaged
633
+ # whenever the relative spelling came first -- and the fallback then had no file to
634
+ # import. a bare `from . import x` carries the name in `names`, not in `module`.
635
+ if node.module:
636
+ names.add(node.module.split(".", 1)[0])
637
+ else:
638
+ names.update(alias.name.split(".", 1)[0] for alias in node.names)
639
+ # level >= 2 climbs above the package this push carries, so it can name no sibling.
619
640
  elif isinstance(node, ast.Call):
620
641
  dynamic = _dynamic_import_name(node, callees)
621
642
  if dynamic:
@@ -623,24 +644,109 @@ def _imported_module_names(tree) -> set[str]:
623
644
  return names
624
645
 
625
646
 
626
- def _helper_imports(helper: Path) -> set[str]:
647
+ def _helper_imports(helper: Path, *, env_root: Path) -> set[str]:
627
648
  """Top-level names a packaged helper imports, for following its own dependencies.
628
649
 
629
650
  A helper that fails to parse is still shipped verbatim: refusing the push over a file the
630
651
  sidecar may never execute would be a harsher failure than the ModuleNotFoundError this
631
- lookahead exists to avoid, and the sidecar's own syntax is validated separately."""
652
+ lookahead exists to avoid, and the sidecar's own syntax is validated separately.
653
+
654
+ Only a helper sitting directly at `env_root` has root-level siblings, so a relative import
655
+ from deeper in a packaged subdirectory is dropped rather than resolved against the wrong
656
+ directory -- the package walk that reached it already ships its true siblings."""
632
657
  import ast
633
658
 
634
659
  if helper.suffix != ".py":
635
660
  return set()
636
661
  try:
637
662
  return _imported_module_names(
638
- ast.parse(helper.read_text(encoding="utf-8"), filename=str(helper))
663
+ ast.parse(helper.read_text(encoding="utf-8"), filename=str(helper)),
664
+ relative_names_are_siblings=helper.parent == env_root,
639
665
  )
640
666
  except (OSError, SyntaxError, UnicodeDecodeError):
641
667
  return set()
642
668
 
643
669
 
670
+ def _iter_import_closure(
671
+ seed_modules: set[str], *, env_root: Path, entrypoint: Path, yielded: set[Path]
672
+ ) -> Iterator[tuple[Path, Path]]:
673
+ """Yield the sibling helpers reachable from `seed_modules`, following their imports too.
674
+
675
+ A helper that imports another sibling needs that sibling too: shipping `scorer.py` without
676
+ the `thresholds` it imports published an environment that raised ModuleNotFoundError on its
677
+ first case, having passed every local check because the source directory was importable
678
+ (cursor[bot]). The queue grows only through siblings actually reached, so it stays a closure
679
+ over files this push already carries rather than a general dependency resolver -- an
680
+ unimported neighbour stays local.
681
+
682
+ `yielded` is shared with the caller's other walks so a helper that IS `dataset/` (or lives
683
+ under it) is not emitted twice; `_check_env_push_limits` would otherwise charge those bytes
684
+ and members twice and reject a tree that is actually under the limit (codex[bot]).
685
+ """
686
+ import os
687
+
688
+ pending = sorted(seed_modules)
689
+ visited: set[str] = set()
690
+ while pending:
691
+ module_name = pending.pop()
692
+ if module_name in visited:
693
+ continue
694
+ visited.add(module_name)
695
+ # `from graders.rules import score` resolves to a sibling PACKAGE, not graders.py.
696
+ # matching only the module spelling published an environment whose sidecar raises
697
+ # ModuleNotFoundError on its first case, having passed every local check.
698
+ #
699
+ # resolution follows python's own order, which is NOT simply "directories first".
700
+ # a directory holding __init__.py is a regular package and outranks a same-named
701
+ # module, so shipping the .py and skipping the package published the file the
702
+ # sidecar never imports while dropping the one it does (cursor[bot]). but a PEP 420
703
+ # namespace directory (no __init__.py) is only a fallback portion: `graders.py`
704
+ # wins over a bare `graders/`. requiring the marker unconditionally sent a
705
+ # namespace helper to the .py fallback, which did not exist either, and published
706
+ # an archive missing the helper entirely (codex[bot]). verified by probe: with both
707
+ # present, `import graders` binds graders.py.
708
+ package = env_root / module_name
709
+ helper = env_root / f"{module_name}.py"
710
+ ships_package = (
711
+ package.is_dir()
712
+ and not package.is_symlink()
713
+ and ((package / "__init__.py").is_file() or not helper.is_file())
714
+ )
715
+ if ships_package and not _ignore_env_push_path(
716
+ package, env_root=env_root, entrypoint=entrypoint
717
+ ):
718
+ for root, dirs, files in os.walk(
719
+ package, topdown=True, followlinks=False, onerror=_raise_walk_error
720
+ ):
721
+ root_path = Path(root)
722
+ dirs[:] = sorted(
723
+ name
724
+ for name in dirs
725
+ if not _ignore_env_push_path(
726
+ root_path / name, env_root=env_root, entrypoint=entrypoint
727
+ )
728
+ )
729
+ for name in sorted(files):
730
+ child = root_path / name
731
+ if child in yielded or _ignore_env_push_path(
732
+ child, env_root=env_root, entrypoint=entrypoint
733
+ ):
734
+ continue
735
+ yielded.add(child)
736
+ pending.extend(_helper_imports(child, env_root=env_root))
737
+ yield child, child.relative_to(env_root)
738
+ continue
739
+ if (
740
+ helper.is_file()
741
+ and helper != entrypoint
742
+ and helper not in yielded
743
+ and not _ignore_env_push_path(helper, env_root=env_root, entrypoint=entrypoint)
744
+ ):
745
+ yielded.add(helper)
746
+ pending.extend(_helper_imports(helper, env_root=env_root))
747
+ yield helper, helper.relative_to(env_root)
748
+
749
+
644
750
  def _iter_env_sidecar_files(
645
751
  env_root: Path, *, entrypoint: Path, include_full_tree: bool
646
752
  ) -> Iterator[tuple[Path, Path]]:
@@ -666,6 +772,24 @@ def _iter_env_sidecar_files(
666
772
  ):
667
773
  yielded.add(doc)
668
774
  yield doc, doc.relative_to(env_root)
775
+ # the entrypoint's OWN imports ship, exactly as its evaluation sidecar's do. seeding the
776
+ # closure only from evaluations.py published an environment whose entrypoint imported
777
+ # siblings without them: `env push` exits 0 and prints an id, so nothing surfaces until a
778
+ # GPU has been rented and the worker imports the module. parsed with the same reader as
779
+ # every other module here, so a lazily imported helper (inside load_environment) counts.
780
+ try:
781
+ entry_tree = ast.parse(entrypoint.read_text(encoding="utf-8"), filename=str(entrypoint))
782
+ except (OSError, SyntaxError, UnicodeDecodeError):
783
+ # the entrypoint is validated for syntax on its own path, and a push that cannot read
784
+ # it fails there with a better message. an unreadable file here just means no closure.
785
+ entry_tree = None
786
+ if entry_tree is not None:
787
+ yield from _iter_import_closure(
788
+ _imported_module_names(entry_tree),
789
+ env_root=env_root,
790
+ entrypoint=entrypoint,
791
+ yielded=yielded,
792
+ )
669
793
  # the eval sidecar ships with its entrypoint. `env eval` loads evaluations.py next to an
670
794
  # exact .py target, so omitting it here would publish an environment whose suite passed
671
795
  # locally and is simply absent once pushed.
@@ -675,8 +799,14 @@ def _iter_env_sidecar_files(
675
799
  and sidecar != entrypoint
676
800
  and not _ignore_env_push_path(sidecar, env_root=env_root, entrypoint=entrypoint)
677
801
  ):
678
- yielded.add(sidecar)
679
- yield sidecar, sidecar.relative_to(env_root)
802
+ # the entrypoint closure above shares `yielded` and runs first, so an entrypoint that
803
+ # imports `evaluations` already shipped it. yielding it a second time made
804
+ # `_check_env_push_limits` charge those bytes and that member twice, rejecting a tree
805
+ # that is actually under the limit (codex[bot]). only the yield is skipped: the syntax
806
+ # check and import walk below must still run either way.
807
+ if sidecar not in yielded:
808
+ yielded.add(sidecar)
809
+ yield sidecar, sidecar.relative_to(env_root)
680
810
  try:
681
811
  tree = ast.parse(sidecar.read_text(encoding="utf-8"), filename=str(sidecar))
682
812
  except SyntaxError as exc:
@@ -688,73 +818,12 @@ def _iter_env_sidecar_files(
688
818
  # (flash/envs/evaluations.py). scanning only top-level nodes skips exactly that
689
819
  # pattern, so `env test` passes locally and the pushed environment is missing the
690
820
  # helper the sidecar imports the moment it grades a case.
691
- imported_modules = _imported_module_names(tree)
692
- # a helper that imports another sibling needs that sibling too: shipping `scorer.py`
693
- # without the `thresholds` it imports published an environment that raised
694
- # ModuleNotFoundError on its first case, having passed every local check because the
695
- # source directory was importable (cursor[bot]). the queue is seeded from the sidecar
696
- # and grows only through siblings actually reached, so it stays a closure over files
697
- # this push already carries rather than a general dependency resolver.
698
- pending = sorted(imported_modules)
699
- visited: set[str] = set()
700
- while pending:
701
- module_name = pending.pop()
702
- if module_name in visited:
703
- continue
704
- visited.add(module_name)
705
- # `from graders.rules import score` resolves to a sibling PACKAGE, not graders.py.
706
- # matching only the module spelling published an environment whose sidecar raises
707
- # ModuleNotFoundError on its first case, having passed every local check.
708
- #
709
- # resolution follows python's own order, which is NOT simply "directories first".
710
- # a directory holding __init__.py is a regular package and outranks a same-named
711
- # module, so shipping the .py and skipping the package published the file the
712
- # sidecar never imports while dropping the one it does (cursor[bot]). but a PEP 420
713
- # namespace directory (no __init__.py) is only a fallback portion: `graders.py`
714
- # wins over a bare `graders/`. requiring the marker unconditionally sent a
715
- # namespace helper to the .py fallback, which did not exist either, and published
716
- # an archive missing the helper entirely (codex[bot]). verified by probe: with both
717
- # present, `import graders` binds graders.py.
718
- package = env_root / module_name
719
- helper = env_root / f"{module_name}.py"
720
- ships_package = (
721
- package.is_dir()
722
- and not package.is_symlink()
723
- and ((package / "__init__.py").is_file() or not helper.is_file())
724
- )
725
- if ships_package and not _ignore_env_push_path(
726
- package, env_root=env_root, entrypoint=entrypoint
727
- ):
728
- for root, dirs, files in os.walk(
729
- package, topdown=True, followlinks=False, onerror=_raise_walk_error
730
- ):
731
- root_path = Path(root)
732
- dirs[:] = sorted(
733
- name
734
- for name in dirs
735
- if not _ignore_env_push_path(
736
- root_path / name, env_root=env_root, entrypoint=entrypoint
737
- )
738
- )
739
- for name in sorted(files):
740
- child = root_path / name
741
- if child in yielded or _ignore_env_push_path(
742
- child, env_root=env_root, entrypoint=entrypoint
743
- ):
744
- continue
745
- yielded.add(child)
746
- pending.extend(_helper_imports(child))
747
- yield child, child.relative_to(env_root)
748
- continue
749
- if (
750
- helper.is_file()
751
- and helper != entrypoint
752
- and helper not in yielded
753
- and not _ignore_env_push_path(helper, env_root=env_root, entrypoint=entrypoint)
754
- ):
755
- yielded.add(helper)
756
- pending.extend(_helper_imports(helper))
757
- yield helper, helper.relative_to(env_root)
821
+ yield from _iter_import_closure(
822
+ _imported_module_names(tree),
823
+ env_root=env_root,
824
+ entrypoint=entrypoint,
825
+ yielded=yielded,
826
+ )
758
827
  roots = [
759
828
  env_root / name
760
829
  for name in ("dataset", "datasets")
@@ -7,19 +7,16 @@ policy/reference update.
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
- from flash.providers.allocator import required_vram_gb, vram_headroom
11
- from flash.providers.base import largest_rentable_count
10
+ from flash.providers.allocator import geometry_safe_gpu_cap, required_vram_gb, vram_headroom
12
11
 
13
12
  from .facts import (
14
13
  GPU_COMPUTE_TFLOPS,
15
14
  active_params_b,
16
15
  download_weight_gb,
17
16
  effective_train_tflops,
18
- gpu_hourly_usd,
19
17
  gpu_vram_gb,
20
18
  has_nvlink,
21
19
  model_quant,
22
- pick_gpu,
23
20
  reward_seconds_per_completion,
24
21
  teacher_seconds_per_completion,
25
22
  teacher_token_cost_usd,
@@ -309,16 +306,18 @@ def sft_seconds_for_tokens(config: RunConfig, gpu: str, train_tokens: float) ->
309
306
  return flops / (peak * mfu)
310
307
 
311
308
 
312
- def select_gpu(config: RunConfig, *, max_wall_seconds: float = 0.0) -> tuple[str, int]:
313
- """(chosen GPU class, required VRAM GB): the cheapest fitting class for the cost.
314
-
315
- Uses ``pick_gpu``, which (unlike the submit-time allocator) intentionally stays gate-free —
316
- it considers every fitting class, validated or not — so the estimate reflects the cheapest
317
- card that *could* run the job. The live allocator restricts to the validated pool, so the
318
- actually-provisioned class can be pricier than this. Catalog sizing is offline/deterministic."""
319
- total_params_b(
320
- config.model_id
321
- ) # catalog-only: reject a non-catalog model before any (HF) sizing
309
+ def _offline_gpu_shape(
310
+ config: RunConfig, *, max_wall_seconds: float = 0.0
311
+ ) -> tuple[str, int, int, str, float]:
312
+ """Offline structural quote: (gpu, need, count, provider, per-card rate).
313
+
314
+ Quote preparation must not query live capacity: a sold-out market or transient lookup failure is
315
+ exactly what the lifecycle retry machinery exists to survive, and consuming it here prevents the
316
+ run/status from being created at all. Rank the managed 1/2/4/8-card shapes on the same cost model
317
+ as allocation, then replace this provisional quote with the selected live candidate immediately
318
+ before provisioning.
319
+ """
320
+ total_params_b(config.model_id) # catalog-only; no HF/network sizing in `--cost`
322
321
  need = required_vram_gb(
323
322
  config.model_id,
324
323
  config.method,
@@ -326,12 +325,70 @@ def select_gpu(config: RunConfig, *, max_wall_seconds: float = 0.0) -> tuple[str
326
325
  thinking=config.thinking,
327
326
  model_revision=config.model_revision,
328
327
  )
329
- # rank on job cost, not rental rate, so the quote names the class the allocator will pick.
330
- gpu = pick_gpu(
331
- need,
332
- provider=config.provider,
333
- max_wall_seconds=max_wall_seconds,
334
- cost_key=step_cost_key(config),
328
+ from flash.providers.base import (
329
+ GPU_INFO,
330
+ canonical_gpu,
331
+ combined_vram_gb,
332
+ providers_for,
333
+ rentable_gpu_counts,
334
+ )
335
+
336
+ provider = config.provider if config.provider != "auto" else "auto"
337
+ if config.gpu_type:
338
+ names = (canonical_gpu(config.gpu_type),)
339
+ else:
340
+ names = tuple(
341
+ info.name for info in GPU_INFO.values() if info.enum_member and info.validated
342
+ )
343
+ safe_gpu_count = geometry_safe_gpu_cap(
344
+ config.model_id, config.gpu_count, model_revision=config.model_revision
345
+ )
346
+ ranked = []
347
+ for gpu in names:
348
+ info = GPU_INFO[gpu]
349
+ if provider != "auto" and provider not in providers_for(gpu):
350
+ continue
351
+ for count in rentable_gpu_counts(safe_gpu_count):
352
+ if combined_vram_gb(info.vram_gb, count) < need:
353
+ continue
354
+ # Provisional quoting is structural and must not touch a live market. Vast pricing is
355
+ # offer-backed (therefore capacity-backed), and Lambda's catalog can blip too. Use the
356
+ # provider's offline static rate here; the lifecycle replaces it from the selected candidate
357
+ # before provisioning, so the persisted/charged quote still carries the exact live rate.
358
+ if provider == "lambda":
359
+ from flash.providers.lambdalabs.pricing import static_hourly_rate
360
+
361
+ hourly = static_hourly_rate(gpu)
362
+ else:
363
+ hourly = info.hourly_usd
364
+ gpu_bound, fixed = step_seconds_split(config, gpu)
365
+ step_seconds = gpu_bound / multi_card_speedup(count, gpu) + fixed
366
+ ranked.append(
367
+ (
368
+ hourly * count * step_seconds,
369
+ count,
370
+ combined_vram_gb(info.vram_gb, count),
371
+ info.vram_gb,
372
+ gpu,
373
+ hourly,
374
+ )
375
+ )
376
+ if not ranked:
377
+ if config.gpu_type:
378
+ info = GPU_INFO[canonical_gpu(config.gpu_type)]
379
+ raise ValueError(
380
+ f"exact GPU {info.name!r} cannot fit this run: it requires at least {need} GB"
381
+ )
382
+ shape = f" across up to {safe_gpu_count} cards" if safe_gpu_count > 1 else ""
383
+ raise ValueError(f"no GPU class fits >= {need} GB{shape}")
384
+ _cost, count, _combined, _per_card, gpu, hourly = min(ranked)
385
+ return gpu, need, count, provider, hourly
386
+
387
+
388
+ def select_gpu(config: RunConfig, *, max_wall_seconds: float = 0.0) -> tuple[str, int]:
389
+ """(chosen GPU class, required VRAM GB) from the offline structural quote."""
390
+ gpu, need, _count, _provider, _hourly = _offline_gpu_shape(
391
+ config, max_wall_seconds=max_wall_seconds
335
392
  )
336
393
  return gpu, need
337
394
 
@@ -385,8 +442,15 @@ def _notes(
385
442
  return tuple(notes)
386
443
 
387
444
 
388
- def estimate_cost(config: RunConfig, *, wall_cap_s: float = DEFAULT_WALL_CAP_S) -> CostEstimate:
389
- """Deterministic pre-flight cost calculation."""
445
+ def estimate_cost(
446
+ config: RunConfig, *, wall_cap_s: float = DEFAULT_WALL_CAP_S, allocation=None
447
+ ) -> CostEstimate:
448
+ """Deterministic pre-flight cost calculation.
449
+
450
+ ``allocation`` is the exact live candidate selected by the retrying lifecycle. Preparation omits
451
+ it and stays offline; immediately before provisioning the persisted quote is replaced from this
452
+ candidate so successful billing uses the real provider, class, count, and per-card rate.
453
+ """
390
454
  # Billing cap: mirror the runner's max(60, max_wall_seconds) floor so a sub-60s cap isn't underpriced.
391
455
  cap_s = (
392
456
  max(60.0, float(config.max_wall_seconds))
@@ -404,63 +468,45 @@ def estimate_cost(config: RunConfig, *, wall_cap_s: float = DEFAULT_WALL_CAP_S)
404
468
  market_wall_s = float(config.max_wall_seconds)
405
469
  else:
406
470
  market_wall_s = 0.0
407
- if config.gpu_type:
408
- # gpu_type is provisioned at launch through allocate (disk-aware, live rate). quote the same
409
- # path with either an automatic or pinned provider so the estimate matches the actual launch rate,
410
- # and a disk floor lifts the quote off the wrong class.
411
- from flash.providers.allocator import allocate
412
-
413
- allocation = allocate(
414
- config.model_id,
415
- config.method,
416
- train=config.train_knobs(),
417
- thinking=config.thinking,
418
- max_wall_seconds=market_wall_s,
419
- disk_gb=config.disk_gb,
420
- provider=("" if config.provider == "auto" else config.provider),
421
- gpu_type=config.gpu_type,
422
- model_revision=config.model_revision,
423
- # quote the same card ceiling launch allocates under, so a multi-card run is estimated on
424
- # the combination it will actually rent rather than on a single-class search that ignores it.
425
- max_gpu_count=config.gpu_count,
426
- )
471
+ if allocation is not None:
427
472
  gpu = allocation.gpu
428
473
  quote_provider = allocation.provider
429
- hourly = allocation.hourly_usd
430
- need = allocation.min_vram_gb
431
- # max_gpu_count is a CEILING, so the allocator may fit the run on fewer cards than requested
432
- # (e.g. 2x of a class when 4 was allowed). bill the count it actually chose, not the ceiling.
433
- billed_gpu_count = getattr(allocation, "gpu_count", 1) or 1
474
+ hourly = float(allocation.hourly_usd)
475
+ need = int(
476
+ getattr(allocation, "min_vram_gb", 0)
477
+ or required_vram_gb(
478
+ config.model_id,
479
+ config.method,
480
+ train=config.train_knobs(),
481
+ thinking=config.thinking,
482
+ model_revision=config.model_revision,
483
+ )
484
+ )
485
+ billed_gpu_count = int(getattr(allocation, "gpu_count", 1) or 1)
434
486
  else:
435
- gpu, need = select_gpu(config, max_wall_seconds=market_wall_s)
436
- quote_provider = config.provider
437
- # no gpu_type pin means no allocate() call and so no combination search; this branch picks a
438
- # single class that fits alone, and the run occupies that count of it. gpu.count is a CEILING
439
- # though, and only rentable powers of two are ever provisioned -- billing the raw ceiling
440
- # would charge a `--gpus 3` run for 3 cards when submit rents 2. this quote is persisted at
441
- # submit and charged verbatim by _status_estimated_charge, so an over-count here overbills.
442
- billed_gpu_count = largest_rentable_count(config.gpu_count)
443
- # quote the same vram-floored vast market pick_gpu selected under (min_vram_gb=need): without the
444
- # floor the rate lookup searches from the smallest managed class, letting cheap small-card offers
445
- # crowd a high-vram selection off the limited page -> it silently falls back to the static rate.
446
- hourly = gpu_hourly_usd(
447
- gpu,
448
- provider=quote_provider,
449
- max_wall_seconds=market_wall_s,
450
- min_vram_gb=need,
451
- gpu_type="", # this branch is only reached when gpu_type is empty
487
+ # Preparation and `flash train --cost` must stay independent of live capacity. A provider
488
+ # lookup blip here would consume the first allocation failure before a run/status exists, so
489
+ # the lifecycle could never retry it. This provisional structural quote is replaced from the
490
+ # exact selected candidate immediately before provisioning.
491
+ gpu, need, billed_gpu_count, quote_provider, hourly = _offline_gpu_shape(
492
+ config, max_wall_seconds=market_wall_s
452
493
  )
453
494
 
454
495
  setup = setup_seconds(config)
455
- sps = seconds_per_step(config, gpu)
496
+ gpu_bound_s, fixed_s = step_seconds_split(config, gpu)
497
+ speedup = multi_card_speedup(billed_gpu_count, gpu)
498
+ sps = gpu_bound_s / speedup + fixed_s
456
499
  required_save_s = required_save_overhead_seconds(config)
457
500
  # A one-time kernel/graph compile is paid once on the first step (MoE-only; 0 for dense). It is
458
- # training GPU time, so it belongs in the (billed) train term, not setup.
501
+ # training GPU time, so it belongs in the (billed) train term, not setup. Required saves are also
502
+ # synchronous fixed overhead; neither is divided by the number of cards.
459
503
  compile_s = compile_seconds(config, gpu)
460
504
  raw_train = compile_s + config.steps * sps + required_save_s
461
505
  if not config.is_grpo and config.train_tokens is not None:
462
506
  raw_train = (
463
- compile_s + sft_seconds_for_tokens(config, gpu, config.train_tokens) + required_save_s
507
+ compile_s
508
+ + sft_seconds_for_tokens(config, gpu, config.train_tokens) / speedup
509
+ + required_save_s
464
510
  )
465
511
  sps = raw_train / config.steps
466
512
 
@@ -172,6 +172,6 @@ def runconfig_from_spec(spec) -> RunConfig:
172
172
  )
173
173
 
174
174
 
175
- def estimate_for_spec(spec) -> CostEstimate:
176
- """The pre-flight ``CostEstimate`` for a parsed training ``JobSpec``."""
177
- return estimate_cost(runconfig_from_spec(spec))
175
+ def estimate_for_spec(spec, *, allocation=None) -> CostEstimate:
176
+ """Cost estimate for a parsed training spec, optionally pinned to the selected live candidate."""
177
+ return estimate_cost(runconfig_from_spec(spec), allocation=allocation)
@@ -1207,9 +1207,9 @@ def check_fit(
1207
1207
 
1208
1208
  gpu_gb = GPU_VRAM_GB.get(gpu, 32)
1209
1209
  # clamp to what the ceiling actually BUYS before sizing: only powers of two up to the
1210
- # combination cap are ever rented, so a ceiling of 3 provisions 2 cards and a ceiling of 8
1211
- # provisions 4. sizing on the raw ceiling would accept a shape submit can never allocate --
1212
- # the parse/submit divergence this parameter exists to close, inverted.
1210
+ # combination cap are ever rented, so a ceiling of 3 provisions 2 cards. sizing on the raw
1211
+ # ceiling would accept a shape submit can never allocate -- the parse/submit divergence this
1212
+ # parameter exists to close, inverted.
1213
1213
  cards = largest_rentable_count(gpu_count)
1214
1214
  usable_gb = combined_vram_gb(gpu_gb, cards)
1215
1215
  if params_b is None: