freesolo-flash-dev 1.0.3__tar.gz → 1.0.5__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 (361) hide show
  1. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/Dockerfile.worker +1 -1
  2. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/PKG-INFO +4 -4
  3. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/cli/__init__.py +5 -1
  4. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/cli/commands.py +16 -14
  5. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/cli/envpush.py +192 -54
  6. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/cli/render.py +76 -13
  7. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/cli/training_doc.py +11 -8
  8. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/client/http.py +40 -17
  9. freesolo_flash_dev-1.0.5/flash/engine/multiturn_reward_scoring.py +73 -0
  10. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/engine/multiturn_rollout.py +43 -35
  11. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/engine/worker/__init__.py +6 -0
  12. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/engine/worker/adapter.py +68 -15
  13. freesolo_flash_dev-1.0.5/flash/engine/worker/grpo_multimodal.py +274 -0
  14. freesolo_flash_dev-1.0.5/flash/engine/worker/grpo_perturn_trainer.py +138 -0
  15. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/engine/worker/heartbeat.py +66 -1
  16. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/engine/worker/hf.py +342 -119
  17. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/engine/worker/lora.py +14 -0
  18. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/engine/worker/opd.py +38 -7
  19. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/engine/worker/opd_vllm.py +77 -14
  20. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/engine/worker/rl.py +125 -2
  21. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/engine/worker/sft.py +713 -134
  22. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/engine/worker/teacher.py +111 -1
  23. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/envs/adapter.py +74 -8
  24. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/envs/base.py +40 -0
  25. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/providers/_poll.py +5 -0
  26. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/providers/_worker.py +5 -1
  27. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/schema/__init__.py +37 -0
  28. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/schema/fields.py +23 -1
  29. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/serve/deploy.py +141 -34
  30. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/server/auth.py +44 -19
  31. freesolo_flash_dev-1.0.5/flash/server/db.py +248 -0
  32. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/server/routes/meta.py +8 -1
  33. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/server/routes/serving.py +106 -37
  34. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/spec.py +32 -1
  35. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/pyproject.toml +11 -8
  36. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/conftest.py +2 -0
  37. freesolo_flash_dev-1.0.5/tests/test_async_uploads.py +448 -0
  38. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_checkpoints.py +15 -14
  39. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_cli_commands.py +100 -9
  40. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_cli_render_theme.py +108 -0
  41. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_client.py +58 -6
  42. freesolo_flash_dev-1.0.5/tests/test_client_stream_reads.py +109 -0
  43. freesolo_flash_dev-1.0.5/tests/test_deploy_latency.py +418 -0
  44. freesolo_flash_dev-1.0.5/tests/test_env_push.py +700 -0
  45. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_flash_worker.py +20 -36
  46. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_grpo_params.py +1 -0
  47. freesolo_flash_dev-1.0.5/tests/test_grpo_perturn.py +431 -0
  48. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_health_liveness.py +6 -1
  49. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_kernel_fingerprint.py +18 -0
  50. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_multimodal_training.py +17 -0
  51. freesolo_flash_dev-1.0.5/tests/test_multiturn_per_turn_reward.py +379 -0
  52. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_multiturn_rollout.py +7 -3
  53. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_multiturn_rollout_coverage.py +6 -2
  54. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_multiturn_rollout_request_policy.py +2 -0
  55. freesolo_flash_dev-1.0.5/tests/test_named_reward_metrics.py +79 -0
  56. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_opd.py +168 -33
  57. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_opd_vllm.py +142 -0
  58. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_resume_on_retry.py +2 -0
  59. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_serve.py +44 -9
  60. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_server_api.py +156 -1
  61. freesolo_flash_dev-1.0.5/tests/test_server_auth_singleflight.py +82 -0
  62. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_server_db.py +238 -0
  63. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_serving_contract.py +21 -4
  64. freesolo_flash_dev-1.0.5/tests/test_sft_activation_shaping.py +140 -0
  65. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_sft_example_selection.py +15 -0
  66. freesolo_flash_dev-1.0.5/tests/test_sft_tokenize_cache.py +375 -0
  67. freesolo_flash_dev-1.0.5/tests/test_single_turn_multimodal_grpo.py +368 -0
  68. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_spec_and_validation.py +91 -2
  69. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_training_controls.py +2 -0
  70. freesolo_flash_dev-1.0.5/tests/test_warmstart_adapter_download.py +282 -0
  71. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_warmstart_cross_repo.py +15 -1
  72. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_worker_init_heartbeat.py +49 -0
  73. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/uv.lock +4 -4
  74. freesolo_flash_dev-1.0.3/flash/server/db.py +0 -134
  75. freesolo_flash_dev-1.0.3/tests/test_env_push.py +0 -372
  76. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/.claude/skills/verify/SKILL.md +0 -0
  77. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/.dockerignore +0 -0
  78. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/.env.example +0 -0
  79. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/.github/workflows/auto-rebake.yml +0 -0
  80. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/.github/workflows/bake-kernel-cache.yml +0 -0
  81. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/.github/workflows/ci.yml +0 -0
  82. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/.github/workflows/main-source-guard.yml +0 -0
  83. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/.github/workflows/notify-tests-repo.yml +0 -0
  84. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/.github/workflows/publish-dev.yml +0 -0
  85. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/.github/workflows/publish-image.yml +0 -0
  86. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/.github/workflows/publish.yml +0 -0
  87. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/.github/workflows/version-parity.yml +0 -0
  88. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/.github/workflows/worker-image.yml +0 -0
  89. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/.gitignore +0 -0
  90. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/Dockerfile +0 -0
  91. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/LICENSE +0 -0
  92. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/README.md +0 -0
  93. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/build/kernel_cache/.gitignore +0 -0
  94. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/build/kernel_cache/.keep +0 -0
  95. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/docker/Dockerfile.kernelcache +0 -0
  96. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/docker/Dockerfile.kernelcache.relayer +0 -0
  97. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/docker/bake_kernel_cache.py +0 -0
  98. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/docker/bake_pod_entry.py +0 -0
  99. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/docker/kernel_fingerprint.py +0 -0
  100. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/docker/make_rp_handler.py +0 -0
  101. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/__init__.py +0 -0
  102. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/_channel.py +0 -0
  103. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/_fileio.py +0 -0
  104. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/_logging.py +0 -0
  105. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/_update_check.py +0 -0
  106. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/adapter_artifacts.py +0 -0
  107. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/catalog.py +0 -0
  108. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/cli/__main__.py +0 -0
  109. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/cli/_tty.py +0 -0
  110. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/cli/env_setup.py +0 -0
  111. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/client/__init__.py +0 -0
  112. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/client/config.py +0 -0
  113. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/client/runtime_secrets.py +0 -0
  114. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/client/specs.py +0 -0
  115. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/cost/__init__.py +0 -0
  116. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/cost/analytical.py +0 -0
  117. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/cost/facts.py +0 -0
  118. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/cost/spec.py +0 -0
  119. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/cost/types.py +0 -0
  120. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/diagnostics.py +0 -0
  121. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/engine/__init__.py +0 -0
  122. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/engine/accounting.py +0 -0
  123. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/engine/chalk_kernels.py +0 -0
  124. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/engine/recipe.py +0 -0
  125. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/engine/steps.py +0 -0
  126. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/engine/structured_outputs.py +0 -0
  127. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/engine/vram.py +0 -0
  128. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/engine/worker/__main__.py +0 -0
  129. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/engine/worker/_pkg.py +0 -0
  130. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/engine/worker/decoding.py +0 -0
  131. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/engine/worker/finalize.py +0 -0
  132. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/engine/worker/gpu_setup.py +0 -0
  133. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/engine/worker/grpo.py +0 -0
  134. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/engine/worker/kernel_warmup.py +0 -0
  135. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/engine/worker/opd_gkd.py +0 -0
  136. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/engine/worker/packing.py +0 -0
  137. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/engine/worker/perf/__init__.py +0 -0
  138. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/engine/worker/perf/attn.py +0 -0
  139. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/engine/worker/perf/diagnostics.py +0 -0
  140. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/engine/worker/perf/lifecycle.py +0 -0
  141. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/engine/worker/perf/liger.py +0 -0
  142. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/engine/worker/perf/loraplus.py +0 -0
  143. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/engine/worker/perf/memory.py +0 -0
  144. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/engine/worker/rng.py +0 -0
  145. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/engine/worker/tokenizer_align.py +0 -0
  146. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/engine/worker/wandb_log.py +0 -0
  147. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/engine/worker_entrypoint.py +0 -0
  148. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/envs/__init__.py +0 -0
  149. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/envs/archive.py +0 -0
  150. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/envs/archive_policy.py +0 -0
  151. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/envs/loader.py +0 -0
  152. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/envs/pull.py +0 -0
  153. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/envs/registry.py +0 -0
  154. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/lora_rank.py +0 -0
  155. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/multimodal.py +0 -0
  156. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/opd_retry_contract.py +0 -0
  157. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/providers/__init__.py +0 -0
  158. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/providers/_auth.py +0 -0
  159. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/providers/_deadline.py +0 -0
  160. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/providers/_hf_artifacts.py +0 -0
  161. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/providers/_hf_retry.py +0 -0
  162. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/providers/_http.py +0 -0
  163. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/providers/_instance.py +0 -0
  164. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/providers/_instance_bootstrap.py +0 -0
  165. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/providers/_instance_poll.py +0 -0
  166. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/providers/_instance_provider.py +0 -0
  167. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/providers/allocator.py +0 -0
  168. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/providers/base.py +0 -0
  169. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/providers/lambdalabs/__init__.py +0 -0
  170. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/providers/lambdalabs/api.py +0 -0
  171. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/providers/lambdalabs/auth.py +0 -0
  172. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/providers/lambdalabs/gpus.py +0 -0
  173. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/providers/lambdalabs/jobs/__init__.py +0 -0
  174. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/providers/lambdalabs/jobs/builders.py +0 -0
  175. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/providers/lambdalabs/preflight.py +0 -0
  176. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/providers/lambdalabs/pricing.py +0 -0
  177. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/providers/preflight.py +0 -0
  178. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/providers/realized.py +0 -0
  179. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/providers/runpod/__init__.py +0 -0
  180. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/providers/runpod/api.py +0 -0
  181. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/providers/runpod/auth.py +0 -0
  182. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/providers/runpod/cost.py +0 -0
  183. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/providers/runpod/gpus.py +0 -0
  184. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/providers/runpod/jobs.py +0 -0
  185. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/providers/runpod/keys.py +0 -0
  186. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/providers/runpod/preflight.py +0 -0
  187. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/providers/runpod/preload.py +0 -0
  188. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/providers/runpod/pricing.py +0 -0
  189. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/providers/runpod/slots.py +0 -0
  190. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/providers/runpod/train/__init__.py +0 -0
  191. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/providers/runpod/train/endpoints.py +0 -0
  192. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/providers/vast/__init__.py +0 -0
  193. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/providers/vast/api.py +0 -0
  194. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/providers/vast/auth.py +0 -0
  195. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/providers/vast/jobs/__init__.py +0 -0
  196. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/providers/vast/jobs/builders.py +0 -0
  197. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/providers/vast/preflight.py +0 -0
  198. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/providers/vast/pricing.py +0 -0
  199. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/py.typed +0 -0
  200. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/runner/__init__.py +0 -0
  201. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/runner/checkpoints.py +0 -0
  202. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/runner/deploy.py +0 -0
  203. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/runner/lifecycle.py +0 -0
  204. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/runner/verified_revisions.py +0 -0
  205. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/serve/__init__.py +0 -0
  206. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/serve/export.py +0 -0
  207. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/serve/pricing.py +0 -0
  208. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/serve/urls.py +0 -0
  209. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/server/__init__.py +0 -0
  210. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/server/__main__.py +0 -0
  211. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/server/_deps.py +0 -0
  212. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/server/_internal_client.py +0 -0
  213. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/server/_locks.py +0 -0
  214. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/server/_runtime.py +0 -0
  215. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/server/app.py +0 -0
  216. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/server/billing.py +0 -0
  217. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/server/billing_retry.py +0 -0
  218. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/server/checkpoints.py +0 -0
  219. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/server/environment_registry.py +0 -0
  220. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/server/envs.py +0 -0
  221. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/server/reconcile.py +0 -0
  222. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/server/repo_cleanup.py +0 -0
  223. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/server/routes/__init__.py +0 -0
  224. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/server/routes/envs.py +0 -0
  225. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/server/routes/runs.py +0 -0
  226. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/flash/server/run_registry.py +0 -0
  227. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/infisical-entrypoint.sh +0 -0
  228. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/scripts/build_dev_dist.py +0 -0
  229. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/__init__.py +0 -0
  230. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/_helpers/__init__.py +0 -0
  231. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/_helpers/runner.py +0 -0
  232. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/_helpers/specs.py +0 -0
  233. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/_helpers/vast.py +0 -0
  234. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/fixtures/math_eval.jsonl +0 -0
  235. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/fixtures/math_train.jsonl +0 -0
  236. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/live/__init__.py +0 -0
  237. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/live/conftest.py +0 -0
  238. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/live/test_lambda_live.py +0 -0
  239. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/live/test_runpod_live.py +0 -0
  240. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/live/test_vast_live.py +0 -0
  241. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_accounting_coverage.py +0 -0
  242. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_agent_flash_cli_contract.py +0 -0
  243. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_algorithms.py +0 -0
  244. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_allocator.py +0 -0
  245. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_b200_rollout_opt.py +0 -0
  246. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_backend_jobspec_contract.py +0 -0
  247. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_base_model_provenance.py +0 -0
  248. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_billing_retry.py +0 -0
  249. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_cancel_remote.py +0 -0
  250. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_catalog_consistency.py +0 -0
  251. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_chalk_kernels.py +0 -0
  252. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_charge_pricing.py +0 -0
  253. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_cli_commands_coverage.py +0 -0
  254. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_cli_errors.py +0 -0
  255. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_cli_estimate.py +0 -0
  256. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_cli_help.py +0 -0
  257. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_cli_main_coverage.py +0 -0
  258. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_cli_managed.py +0 -0
  259. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_cli_render_coverage.py +0 -0
  260. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_client_server_integration.py +0 -0
  261. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_config_overrides.py +0 -0
  262. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_controlled_experiment_repairs.py +0 -0
  263. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_cost_analytical.py +0 -0
  264. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_cost_equation.py +0 -0
  265. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_cost_estimate.py +0 -0
  266. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_cost_hardware.py +0 -0
  267. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_cost_models.py +0 -0
  268. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_cost_rewards.py +0 -0
  269. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_dev_channel.py +0 -0
  270. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_disk_gb.py +0 -0
  271. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_endpoint_name.py +0 -0
  272. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_env_cache_evict.py +0 -0
  273. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_env_delete.py +0 -0
  274. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_env_download.py +0 -0
  275. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_env_download_api.py +0 -0
  276. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_env_no_install.py +0 -0
  277. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_env_publish.py +0 -0
  278. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_env_pull.py +0 -0
  279. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_env_pull_coverage.py +0 -0
  280. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_env_pull_loader_coverage.py +0 -0
  281. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_env_pull_managed_control_plane.py +0 -0
  282. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_env_rate_limit_resolve.py +0 -0
  283. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_envs_coverage.py +0 -0
  284. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_export.py +0 -0
  285. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_flash_mvp.py +0 -0
  286. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_flashinfer_cache_dirs.py +0 -0
  287. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_github_urlopen_retry.py +0 -0
  288. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_gpus.py +0 -0
  289. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_grpo_mask_aware.py +0 -0
  290. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_grpo_sleep_gate.py +0 -0
  291. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_hf_retry.py +0 -0
  292. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_idle_endpoint_reaper.py +0 -0
  293. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_instance_bootstrap_coverage.py +0 -0
  294. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_internal_client.py +0 -0
  295. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_jobs.py +0 -0
  296. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_kernel_cache.py +0 -0
  297. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_kv_util.py +0 -0
  298. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_lambda_api_coverage.py +0 -0
  299. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_lambda_runner.py +0 -0
  300. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_logging.py +0 -0
  301. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_login_perms.py +0 -0
  302. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_lora_rank_coverage.py +0 -0
  303. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_lora_rank_preflight.py +0 -0
  304. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_managed_hf_repo.py +0 -0
  305. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_metrics_schema_agent_contract.py +0 -0
  306. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_mig_guard.py +0 -0
  307. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_multimodal_input_grads.py +0 -0
  308. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_multiturn_image_rollout.py +0 -0
  309. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_multiturn_rollout_records.py +0 -0
  310. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_oom_escalate_gpu.py +0 -0
  311. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_opd_full_state_resume.py +0 -0
  312. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_opd_resume_safety.py +0 -0
  313. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_open_model_policy.py +0 -0
  314. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_orchestrator_flash.py +0 -0
  315. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_packing.py +0 -0
  316. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_packing_coverage.py +0 -0
  317. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_poll_helpers.py +0 -0
  318. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_preflight.py +0 -0
  319. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_provider_preflight_coverage.py +0 -0
  320. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_provider_routing.py +0 -0
  321. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_provider_teardown_robustness.py +0 -0
  322. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_providers_symmetry.py +0 -0
  323. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_realized_cost.py +0 -0
  324. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_repo_cleanup.py +0 -0
  325. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_resolve_params_b.py +0 -0
  326. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_runmgmt.py +0 -0
  327. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_runpod_api_delete.py +0 -0
  328. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_runpod_key_fingerprint.py +0 -0
  329. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_runpod_key_waterfall.py +0 -0
  330. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_runpod_slots.py +0 -0
  331. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_server_app_coverage.py +0 -0
  332. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_server_billing.py +0 -0
  333. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_server_checkpoints_coverage.py +0 -0
  334. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_server_envs_coverage.py +0 -0
  335. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_server_locks_coverage.py +0 -0
  336. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_serving_context_preflight.py +0 -0
  337. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_serving_pricing_coverage.py +0 -0
  338. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_serving_schema_coverage.py +0 -0
  339. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_sft_gc_off.py +0 -0
  340. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_sft_max_context.py +0 -0
  341. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_structured_outputs.py +0 -0
  342. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_thinking_config.py +0 -0
  343. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_train_context_preflight.py +0 -0
  344. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_update_check.py +0 -0
  345. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_vast_api.py +0 -0
  346. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_vast_api_coverage.py +0 -0
  347. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_vast_offers.py +0 -0
  348. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_vast_provider_coverage.py +0 -0
  349. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_vast_runner.py +0 -0
  350. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_verified_revisions.py +0 -0
  351. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_verifiers.py +0 -0
  352. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_version.py +0 -0
  353. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_vl_warmstart_adapter_keys.py +0 -0
  354. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_wandb_log_coverage.py +0 -0
  355. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_wandb_naming.py +0 -0
  356. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_weight_cache.py +0 -0
  357. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_worker_dryrun.py +0 -0
  358. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_worker_hardexit.py +0 -0
  359. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_worker_image.py +0 -0
  360. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_worker_stack.py +0 -0
  361. {freesolo_flash_dev-1.0.3 → freesolo_flash_dev-1.0.5}/tests/test_worker_thinking.py +0 -0
@@ -55,7 +55,7 @@ RUN pip install --no-cache-dir \
55
55
  "flashinfer-python==0.6.6" \
56
56
  "bitsandbytes>=0.49" \
57
57
  "datasets>=4.7,<6" \
58
- "huggingface_hub>=0.25" \
58
+ "huggingface_hub>=1.2.0" \
59
59
  "accelerate>=1.4" \
60
60
  "wandb>=0.17" \
61
61
  "flash-linear-attention @ git+https://github.com/fla-org/flash-linear-attention.git@f0e213dbd8b5fb90c3c7eca869ac1706d5377139" \
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: freesolo-flash-dev
3
- Version: 1.0.3
3
+ Version: 1.0.5
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
@@ -20,7 +20,7 @@ Requires-Dist: datasets>=2.19; extra == 'dev'
20
20
  Requires-Dist: fastapi; extra == 'dev'
21
21
  Requires-Dist: freesolo>=0.2.54; extra == 'dev'
22
22
  Requires-Dist: httpx>=0.27; extra == 'dev'
23
- Requires-Dist: huggingface-hub>=0.34; extra == 'dev'
23
+ Requires-Dist: huggingface-hub>=1.2.0; extra == 'dev'
24
24
  Requires-Dist: jsonschema>=4.23; extra == 'dev'
25
25
  Requires-Dist: mypy>=1.13.0; extra == 'dev'
26
26
  Requires-Dist: pytest>=9.0.3; extra == 'dev'
@@ -35,7 +35,7 @@ Requires-Dist: accelerate>=1.4; extra == 'gpu'
35
35
  Requires-Dist: bitsandbytes>=0.49; extra == 'gpu'
36
36
  Requires-Dist: datasets>=2.19; extra == 'gpu'
37
37
  Requires-Dist: freesolo>=0.2.54; extra == 'gpu'
38
- Requires-Dist: huggingface-hub>=0.34; extra == 'gpu'
38
+ Requires-Dist: huggingface-hub>=1.2.0; extra == 'gpu'
39
39
  Requires-Dist: peft>=0.19; extra == 'gpu'
40
40
  Requires-Dist: torch==2.10.0; extra == 'gpu'
41
41
  Requires-Dist: transformers<5.11,>=5.6; extra == 'gpu'
@@ -46,7 +46,7 @@ Requires-Dist: datasets>=2.19; extra == 'server'
46
46
  Requires-Dist: fastapi; extra == 'server'
47
47
  Requires-Dist: freesolo>=0.2.54; extra == 'server'
48
48
  Requires-Dist: httpx>=0.27; extra == 'server'
49
- Requires-Dist: huggingface-hub>=0.34; extra == 'server'
49
+ Requires-Dist: huggingface-hub>=1.2.0; extra == 'server'
50
50
  Requires-Dist: jsonschema>=4.23; extra == 'server'
51
51
  Requires-Dist: referencing>=0.28.4; extra == 'server'
52
52
  Requires-Dist: regex>=2024.11.6; extra == 'server'
@@ -424,7 +424,11 @@ def _build_parser() -> argparse.ArgumentParser:
424
424
  deployments.set_defaults(func=cmd_deployments)
425
425
 
426
426
  chat = sub.add_parser("chat", help="chat with a deployed adapter")
427
- chat.add_argument("run_id")
427
+ chat.add_argument(
428
+ "run_id",
429
+ help="a bare RUN_ID (its current deployment), RUN_ID/step-N (a specific deployed "
430
+ "checkpoint), or a full immutable adapter revision",
431
+ )
428
432
  chat.add_argument("-m", "--message", required=True)
429
433
  chat.add_argument(
430
434
  "--system",
@@ -160,8 +160,9 @@ def cmd_gpus(args) -> int:
160
160
  (info for info in GPU_INFO.values() if info.enum_member), key=lambda g: g.hourly_usd
161
161
  )
162
162
  tip = (
163
- "Tip: GPU class selection is fully automatic the submit-time allocator always picks the\n"
164
- "cheapest validated managed class that fits the model, so you don't pin a GPU type."
163
+ "Tip: GPU allocation is automatic by default.\n"
164
+ "The allocator picks the cheapest validated class that fits. Pin a specific class by "
165
+ 'adding exact_type = "<CLASS>" to the [gpu] section.'
165
166
  )
166
167
  if render.styled():
167
168
  rows = [(info.name, info.vram_gb, runpod_rates.get(info.name)) for info in infos]
@@ -383,18 +384,26 @@ def _log_follow_progress(status: dict | None, fallback_state: str) -> tuple[str,
383
384
  parts = [state]
384
385
  heartbeat = status.get("last_heartbeat") if isinstance(status, dict) else None
385
386
  if isinstance(heartbeat, dict):
387
+ heartbeat_age_seconds = render._heartbeat_age_seconds(heartbeat.get("ts"))
386
388
  stage = heartbeat.get("stage")
387
389
  if stage:
388
390
  parts.append(f"stage={stage}")
391
+ if state == "running":
392
+ warmup = render.warmup_message(
393
+ stage,
394
+ heartbeat_age_seconds,
395
+ render.heartbeat_is_current_attempt(status, heartbeat),
396
+ )
397
+ if warmup:
398
+ parts.append(warmup)
389
399
  step = heartbeat.get("step")
390
400
  if step is not None:
391
401
  parts.append(f"step={step}")
392
402
  # live heartbeat age so a long quiet phase reads as "alive, throttled" not "frozen".
393
403
  # minute granularity: the non-TTY follow path prints a line whenever this string changes,
394
404
  # so a seconds-precision age would emit one line per poll.
395
- ts = heartbeat.get("ts")
396
- if isinstance(ts, (int, float)) and ts > 0:
397
- mins = int(max(0.0, time.time() - ts) // 60)
405
+ if heartbeat_age_seconds is not None:
406
+ mins = int(heartbeat_age_seconds // 60)
398
407
  parts.append(f"hb={mins}m" if mins else "hb=<1m")
399
408
  realized = status.get("realized_cost_usd")
400
409
  if realized is not None:
@@ -737,18 +746,11 @@ def cmd_chat(args) -> int:
737
746
  if revision is None and parsed is None:
738
747
  print(
739
748
  f"invalid chat target {args.run_id!r} "
740
- "(expected a bare <run_id> or full immutable adapter revision)",
741
- file=sys.stderr,
742
- )
743
- return 1
744
- if revision is None and parsed[1] is not None:
745
- print(
746
- "RUN_ID/step-N is not a valid chat target because it would route through the mutable "
747
- "run alias; use the full immutable adapter revision returned by `flash deployments`",
749
+ "(expected a bare <run_id>, <run_id>/step-N, or full immutable adapter revision)",
748
750
  file=sys.stderr,
749
751
  )
750
752
  return 1
751
- chat_target = args.run_id if revision is not None else parsed[0]
753
+ chat_target = args.run_id
752
754
  client = client_from_config()
753
755
  messages = [{"role": "user", "content": args.message}]
754
756
  system = getattr(args, "system", None)
@@ -11,6 +11,7 @@ import os
11
11
  import re
12
12
  import sys
13
13
  import tempfile
14
+ from collections.abc import Iterator
14
15
  from pathlib import Path
15
16
  from typing import TYPE_CHECKING, Literal, TypeAlias
16
17
 
@@ -174,6 +175,10 @@ def cmd_env_delete(args) -> int:
174
175
 
175
176
 
176
177
  _ENV_ENTRYPOINT = "environment.py"
178
+ _ENV_SYSPATH_BOOTSTRAP = (
179
+ "import os as _flash_os, sys as _flash_sys\n"
180
+ "_flash_sys.path.insert(0, _flash_os.path.dirname(__file__))\n"
181
+ )
177
182
  _ENV_PUSH_IGNORED_NAMES = frozenset(
178
183
  {
179
184
  ".prime",
@@ -183,26 +188,47 @@ _ENV_PUSH_IGNORED_NAMES = frozenset(
183
188
  ".venv",
184
189
  ".mypy_cache",
185
190
  ".pytest_cache",
186
- "pyproject.toml",
187
- "source",
191
+ ".ruff_cache",
192
+ ".idea",
193
+ ".vscode",
194
+ ".ds_store",
195
+ ".git-worktrees",
196
+ ".env",
197
+ ".netrc",
198
+ ".aws",
199
+ ".ssh",
200
+ "venv",
201
+ "node_modules",
202
+ "credentials",
203
+ "secrets",
188
204
  }
189
205
  )
190
- # ``.md`` is included so the ``TRAINING.md`` playbook `flash env setup` scaffolds (and any
191
- # user-authored README/NOTES) travels with the env into the hub and back out through
192
- # ``flash env pull`` — a published env should carry its own training guidance, not just code+data.
193
- _ENV_PUSH_SIDECAR_SUFFIXES = frozenset(
194
- {
195
- ".csv",
196
- ".json",
197
- ".jsonl",
198
- ".md",
199
- ".parquet",
200
- ".tsv",
201
- ".txt",
202
- ".yaml",
203
- ".yml",
204
- }
206
+ _ENV_PUSH_ROOT_IGNORED_NAMES = frozenset({"pyproject.toml", "source"})
207
+ _ENV_PUSH_SECRET_PATTERNS = (
208
+ ".env.*",
209
+ "*.env",
210
+ "*.pem",
211
+ "*.key",
212
+ "*.pfx",
213
+ "*.p12",
214
+ "credentials*",
215
+ "id_rsa*",
216
+ "id_dsa*",
217
+ "id_ecdsa*",
218
+ "id_ed25519*",
205
219
  )
220
+ # secret patterns match secret data FILES only (see _ignore_env_push_path): they never prune a
221
+ # directory, so a package tree like credentials_store/ still ships, and python source is exempt,
222
+ # so helper modules like credentials.py or id_ed25519_loader.py travel instead of being silently
223
+ # dropped and breaking the worker with ModuleNotFoundError. that keeps the prefixes broad enough
224
+ # to catch backup variants (credentials_backup, id_rsa_backup) without eating code or packages.
225
+ _ENV_PUSH_CODE_SUFFIXES = frozenset({".py", ".pyi"})
226
+ _ENV_PUSH_MAX_TOTAL_BYTES = 256 * 1024 * 1024
227
+
228
+ try:
229
+ from flash.envs.archive_policy import ARCHIVE_MEMBER_LIMIT as _ENV_PUSH_MAX_FILES
230
+ except ImportError:
231
+ _ENV_PUSH_MAX_FILES = 5000
206
232
 
207
233
 
208
234
  def _normalize_env_name(raw: str) -> str | None:
@@ -227,16 +253,12 @@ def _normalize_env_name(raw: str) -> str | None:
227
253
 
228
254
  def _with_syspath_bootstrap(env_source: str) -> str:
229
255
  """Prepend a sys.path bootstrap so a published env can resolve shipped sibling helpers."""
230
- bootstrap = (
231
- "import os as _flash_os, sys as _flash_sys\n"
232
- "_flash_sys.path.insert(0, _flash_os.path.dirname(__file__))\n"
233
- )
234
256
  import ast
235
257
 
236
258
  try:
237
259
  tree = ast.parse(env_source)
238
260
  except SyntaxError:
239
- return bootstrap + env_source
261
+ return _ENV_SYSPATH_BOOTSTRAP + env_source
240
262
  insert_after = 0
241
263
  body = tree.body
242
264
  i = 0
@@ -252,7 +274,16 @@ def _with_syspath_bootstrap(env_source: str) -> str:
252
274
  insert_after = body[i].end_lineno or insert_after
253
275
  i += 1
254
276
  lines = env_source.splitlines(keepends=True)
255
- return "".join(lines[:insert_after]) + bootstrap + "".join(lines[insert_after:])
277
+ return (
278
+ "".join(lines[:insert_after])
279
+ + _ENV_SYSPATH_BOOTSTRAP
280
+ + "".join(lines[insert_after:])
281
+ )
282
+
283
+
284
+ def _raise_walk_error(error: OSError) -> None:
285
+ """fail env packaging loudly instead of silently skipping an unreadable directory."""
286
+ raise error
256
287
 
257
288
 
258
289
  def _tar_b64(directory: Path) -> str:
@@ -266,44 +297,130 @@ def _tar_b64(directory: Path) -> str:
266
297
  with tarfile.open(fileobj=buf, mode="w:gz") as tar:
267
298
  for root, dirs, files in os.walk(directory):
268
299
  root_path = Path(root)
269
- dirs[:] = sorted(d for d in dirs if d not in _ENV_PUSH_IGNORED_NAMES)
270
- for name in dirs:
271
- child = root_path / name
272
- tar.add(child, arcname=str(child.relative_to(directory)), recursive=False)
300
+ dirs[:] = sorted(d for d in dirs if d.lower() not in _ENV_PUSH_IGNORED_NAMES)
273
301
  for name in sorted(files):
274
302
  child = root_path / name
275
303
  tar.add(child, arcname=str(child.relative_to(directory)), recursive=False)
276
304
  return base64.b64encode(buf.getvalue()).decode()
277
305
 
278
306
 
279
- def _copy_env_sidecars(env_root: Path, dest: Path, *, entrypoint: Path) -> None:
280
- """Copy helper code and data sidecars beside environment.py."""
281
- import shutil
307
+ def _ignore_env_push_path(path: Path, *, env_root: Path, entrypoint: Path) -> bool:
308
+ """Return whether a source path must not be included in an env package."""
309
+ import fnmatch
310
+
311
+ name = path.name
312
+ lowered_name = name.lower()
313
+ if path.is_symlink():
314
+ return True
315
+ if path.is_dir() and (path / "pyvenv.cfg").is_file():
316
+ return True
317
+ if path == entrypoint or (
318
+ path.parent == env_root and lowered_name == _ENV_ENTRYPOINT.lower()
319
+ ):
320
+ return True
321
+ if lowered_name.startswith(".") or lowered_name in _ENV_PUSH_IGNORED_NAMES:
322
+ return True
323
+ if path.parent == env_root and lowered_name in _ENV_PUSH_ROOT_IGNORED_NAMES:
324
+ return True
325
+ if (
326
+ path.is_file()
327
+ and path.suffix.lower() not in _ENV_PUSH_CODE_SUFFIXES
328
+ and any(fnmatch.fnmatchcase(lowered_name, pattern) for pattern in _ENV_PUSH_SECRET_PATTERNS)
329
+ ):
330
+ return True
331
+ return not (path.is_dir() or path.is_file())
332
+
333
+
334
+ def _iter_env_sidecar_files(
335
+ env_root: Path, *, entrypoint: Path, include_full_tree: bool
336
+ ) -> Iterator[tuple[Path, Path]]:
337
+ """Yield publishable sidecar files and their package-relative paths."""
338
+ import os
282
339
 
283
- for child in sorted(env_root.iterdir()):
284
- if (
285
- child == entrypoint
286
- or child.name == _ENV_ENTRYPOINT
287
- or child.name in _ENV_PUSH_IGNORED_NAMES
340
+ roots = [env_root]
341
+ if not include_full_tree:
342
+ # a single-file push carries only the entrypoint, its dataset sidecars, and its own
343
+ # docs. ship user-authored docs so the synthesized stub readme does not stand in for
344
+ # real training guidance the user shipped next to the module.
345
+ for doc_name in ("README.md", "TRAINING.md"):
346
+ doc = env_root / doc_name
347
+ if doc.is_file() and not _ignore_env_push_path(
348
+ doc, env_root=env_root, entrypoint=entrypoint
349
+ ):
350
+ yield doc, doc.relative_to(env_root)
351
+ roots = [
352
+ env_root / name
353
+ for name in ("dataset", "datasets")
354
+ if (env_root / name).is_dir() and not (env_root / name).is_symlink()
355
+ ]
356
+
357
+ for walk_root in roots:
358
+ for root, dirs, files in os.walk(
359
+ walk_root, topdown=True, followlinks=False, onerror=_raise_walk_error
288
360
  ):
289
- continue
290
- if child.name.startswith("."):
291
- continue
292
- target = dest / child.name
293
- if child.is_dir():
294
- if child.name == "dataset":
295
- shutil.copytree(
296
- child,
297
- target,
298
- ignore=shutil.ignore_patterns(*_ENV_PUSH_IGNORED_NAMES),
361
+ root_path = Path(root)
362
+ dirs[:] = sorted(
363
+ name
364
+ for name in dirs
365
+ if not _ignore_env_push_path(
366
+ root_path / name, env_root=env_root, entrypoint=entrypoint
299
367
  )
300
- continue
301
- if not child.is_file():
302
- continue
303
- if (
304
- child.suffix == ".py" and not child.name.startswith("__")
305
- ) or child.suffix.lower() in _ENV_PUSH_SIDECAR_SUFFIXES:
306
- shutil.copy2(child, target)
368
+ )
369
+ for name in sorted(files):
370
+ child = root_path / name
371
+ if _ignore_env_push_path(child, env_root=env_root, entrypoint=entrypoint):
372
+ continue
373
+ yield child, child.relative_to(env_root)
374
+
375
+
376
+ def _check_env_push_limits(
377
+ env_root: Path, *, entrypoint: Path, include_full_tree: bool, env_name: str
378
+ ) -> None:
379
+ """Reject oversized source trees before copying or building an in-memory archive."""
380
+ files = 1
381
+ total_bytes = entrypoint.stat().st_size + len(_ENV_SYSPATH_BOOTSTRAP.encode())
382
+ directories: set[Path] = set()
383
+ has_readme = False
384
+ for child, relative in _iter_env_sidecar_files(
385
+ env_root, entrypoint=entrypoint, include_full_tree=include_full_tree
386
+ ):
387
+ files += 1
388
+ total_bytes += child.stat().st_size
389
+ has_readme = has_readme or relative == Path("README.md")
390
+ # the server counts every file AND directory against the same member cap when it
391
+ # repackages the checkout for download, so count unique parent dirs here too.
392
+ directories.update(parent for parent in relative.parents if parent != Path("."))
393
+ if not has_readme:
394
+ files += 1
395
+ total_bytes += len(f"# {env_name}\n\nFlash Freesolo environment.\n".encode())
396
+
397
+ members = files + len(directories)
398
+ if total_bytes > _ENV_PUSH_MAX_TOTAL_BYTES:
399
+ raise ValueError(
400
+ f"environment package totals {_human_bytes(total_bytes)} "
401
+ f"(limit {_human_bytes(_ENV_PUSH_MAX_TOTAL_BYTES)}); "
402
+ "remove large artifacts or use a smaller dataset"
403
+ )
404
+ if members > _ENV_PUSH_MAX_FILES:
405
+ raise ValueError(
406
+ f"selected environment contains {members:,} files and directories "
407
+ f"(limit {_ENV_PUSH_MAX_FILES:,}); "
408
+ "remove large artifacts or use a smaller dataset"
409
+ )
410
+
411
+
412
+ def _copy_env_sidecars(
413
+ env_root: Path, dest: Path, *, entrypoint: Path, include_full_tree: bool
414
+ ) -> None:
415
+ """Copy publishable sidecar files beside environment.py, creating directories lazily."""
416
+ import shutil
417
+
418
+ for child, relative in _iter_env_sidecar_files(
419
+ env_root, entrypoint=entrypoint, include_full_tree=include_full_tree
420
+ ):
421
+ target = dest / relative
422
+ target.parent.mkdir(parents=True, exist_ok=True)
423
+ shutil.copy2(child, target)
307
424
 
308
425
 
309
426
  def _human_bytes(n: int) -> str:
@@ -385,8 +502,11 @@ def cmd_env_push(args) -> int:
385
502
  src = Path(args.path)
386
503
  if not src.exists():
387
504
  return _err(f"no such path: {src}")
505
+ if src.is_symlink():
506
+ return _err(f"cannot publish {src}: symlinks are not allowed")
388
507
 
389
- if src.is_dir():
508
+ include_full_tree = src.is_dir()
509
+ if include_full_tree:
390
510
  canonical_entrypoint = src / _ENV_ENTRYPOINT
391
511
  if canonical_entrypoint.is_file():
392
512
  entrypoint = canonical_entrypoint
@@ -410,11 +530,29 @@ def cmd_env_push(args) -> int:
410
530
  else:
411
531
  return _err(f"cannot publish {src}: expected a Freesolo .py module or an env directory.")
412
532
 
533
+ if entrypoint.is_symlink():
534
+ return _err(f"cannot publish {entrypoint}: symlinks are not allowed")
535
+
536
+ try:
537
+ _check_env_push_limits(
538
+ env_root,
539
+ entrypoint=entrypoint,
540
+ include_full_tree=include_full_tree,
541
+ env_name=env_name,
542
+ )
543
+ except (OSError, ValueError) as exc:
544
+ return _err(f"cannot publish {src}: {exc}")
545
+
413
546
  with tempfile.TemporaryDirectory(prefix="flash-env-push-") as tmp:
414
547
  pkg = Path(tmp)
415
548
  module_source = entrypoint.read_text()
416
549
  (pkg / _ENV_ENTRYPOINT).write_text(_with_syspath_bootstrap(module_source))
417
- _copy_env_sidecars(env_root, pkg, entrypoint=entrypoint)
550
+ _copy_env_sidecars(
551
+ env_root,
552
+ pkg,
553
+ entrypoint=entrypoint,
554
+ include_full_tree=include_full_tree,
555
+ )
418
556
  # Only synthesize a stub README when the env didn't ship its own (now carried as a
419
557
  # ``.md`` sidecar) — don't clobber a user-authored README with boilerplate.
420
558
  readme = pkg / "README.md"
@@ -533,21 +533,76 @@ def _humanize_ts(value) -> str | None:
533
533
  return datetime.datetime.fromtimestamp(value, datetime.UTC).strftime("%Y-%m-%d %H:%M UTC")
534
534
 
535
535
 
536
- def _humanize_age(value) -> str | None:
537
- """Format an epoch seconds value as a compact age ("3m ago"), None for non-numbers."""
536
+ def _heartbeat_age_seconds(value: object) -> float | None:
537
+ """Return heartbeat age in seconds, or None for an unusable timestamp."""
538
538
  if not isinstance(value, (int, float)) or value <= 0:
539
539
  return None
540
- secs = max(0.0, time.time() - value)
541
- if secs < 90:
542
- return f"{int(secs)}s ago"
543
- if secs < 5400:
544
- return f"{int(secs // 60)}m ago"
545
- return f"{secs / 3600:.1f}h ago"
540
+ return max(0.0, time.time() - value)
541
+
542
+
543
+ def _humanize_age_seconds(seconds: float | None) -> str | None:
544
+ if seconds is None:
545
+ return None
546
+ if seconds < 90:
547
+ return f"{int(seconds)}s ago"
548
+ if seconds < 5400:
549
+ return f"{int(seconds // 60)}m ago"
550
+ return f"{seconds / 3600:.1f}h ago"
551
+
552
+
553
+ def _humanize_age(value) -> str | None:
554
+ """Format an epoch seconds value as a compact age ("3m ago"), None for non-numbers."""
555
+ return _humanize_age_seconds(_heartbeat_age_seconds(value))
546
556
 
547
557
 
548
558
  # heartbeat age past which the panel reminds that quiet is normal: worker uploads are throttled
549
- # (240s quiet phases, up to 900s mid-training), so a frozen ts is usually NOT a dead worker.
559
+ # (240s quiet phases, up to 900s mid-training), so a frozen ts is usually not a dead worker.
550
560
  _HB_QUIET_HINT_AFTER_S = 300.0
561
+ _WARMUP_HEARTBEAT_FRESH_FOR_S = 1200.0
562
+ _WARMUP_STAGES = frozenset({"rl_train_start", "rl_initializing"})
563
+
564
+
565
+ def heartbeat_is_current_attempt(obj: dict, heartbeat: dict) -> bool:
566
+ """False only when the heartbeat provably belongs to a superseded retry attempt.
567
+
568
+ Retries reuse the seed's heartbeat path, so a recovered run flips back to ``running`` for the
569
+ replacement worker while ``last_heartbeat`` can still be the previous attempt's setup ping until
570
+ the new worker publishes one. ``remote.attempt`` is the live attempt; ``last_heartbeat.attempt``
571
+ is the one that produced the ping. When the live attempt is known, keep the reassurance only for a
572
+ heartbeat whose attempt matches it. When it is unknown (e.g. a managed status payload that omits
573
+ ``remote``), fall back to ``warmup_message``'s age gating rather than suppress, so warmup still
574
+ reassures on planes that do not surface a live attempt.
575
+ """
576
+ # reuse the poller's attempt-identity contract (a bounded nonnegative int, never a bool, string,
577
+ # or float) so the status display and stall detection agree on what a valid attempt is.
578
+ from flash.providers._poll import _attempt_int
579
+
580
+ remote = obj.get("remote")
581
+ current_attempt = _attempt_int(remote.get("attempt")) if isinstance(remote, dict) else None
582
+ if current_attempt is None:
583
+ return True
584
+ return _attempt_int(heartbeat.get("attempt")) == current_attempt
585
+
586
+
587
+ def warmup_message(
588
+ stage: object,
589
+ heartbeat_age_seconds: float | None,
590
+ from_current_attempt: bool = True,
591
+ ) -> str | None:
592
+ """Explain healthy RL setup stages only while the heartbeat is fresh and from the live attempt."""
593
+ stage_name = str(stage)
594
+ if stage_name not in _WARMUP_STAGES:
595
+ return None
596
+ if not from_current_attempt:
597
+ return None
598
+ if heartbeat_age_seconds is None:
599
+ return None
600
+ if heartbeat_age_seconds > _WARMUP_HEARTBEAT_FRESH_FOR_S:
601
+ return None
602
+ return (
603
+ f"warming up (stage={stage_name}): initializing model, vLLM, and training kernels - "
604
+ "typically several minutes, sometimes 15-20 min; setup is not billed; do not cancel"
605
+ )
551
606
 
552
607
 
553
608
  def _heartbeat_pairs(obj: dict) -> list[tuple[str, str]]:
@@ -562,11 +617,19 @@ def _heartbeat_pairs(obj: dict) -> list[tuple[str, str]]:
562
617
  if hb.get("liveness"):
563
618
  worker += " · alive ping"
564
619
  pairs = [("worker", worker)]
565
- ts = hb.get("ts")
566
- age = _humanize_age(ts)
620
+ heartbeat_age_seconds = _heartbeat_age_seconds(hb.get("ts"))
621
+ running = str(obj.get("state") or "") == "running"
622
+ if running:
623
+ warmup = warmup_message(
624
+ hb.get("stage"),
625
+ heartbeat_age_seconds,
626
+ heartbeat_is_current_attempt(obj, hb),
627
+ )
628
+ if warmup:
629
+ pairs.append(("warmup", warmup))
630
+ age = _humanize_age_seconds(heartbeat_age_seconds)
567
631
  if age:
568
- running = str(obj.get("state") or "") == "running"
569
- if running and isinstance(ts, (int, float)) and (time.time() - ts) > _HB_QUIET_HINT_AFTER_S:
632
+ if running and heartbeat_age_seconds > _HB_QUIET_HINT_AFTER_S:
570
633
  age += _dim(
571
634
  " (heartbeat uploads are throttled; quiet is not dead - check flash log -f <run-id>)"
572
635
  )
@@ -109,6 +109,8 @@ epochs = 1 # one pass over the retained train rows
109
109
  max_examples = 2 # rows to train on (the starter dataset has 2)
110
110
  # max_steps = 100 # positive values set the exact optimizer-update horizon
111
111
  # save_at_steps = [10, 50, 100] # requires max_steps; overrides save_every
112
+ # multi-turn GRPO defaults to one reward per rollout; choose "per_turn" for turn-level credit.
113
+ # credit_assignment = "per_episode"
112
114
  lora_rank = 32
113
115
  lora_alpha = 64
114
116
  # All SFT/GRPO knobs live under [train]. Do not add [sft] or [grpo] tables.
@@ -339,10 +341,12 @@ def score_response(self, example, response_text) -> RewardResult:
339
341
  )
340
342
  ```
341
343
 
342
- `score` is what GRPO optimizes (it becomes the run's `total`). Each `RewardMetric` you
343
- attach is logged by name in the per-scorer breakdown that is how the clean success
344
- rate becomes visible. Use the shaped `score` to confirm the model is learning *at all*,
345
- and judge the run on the explicit `success` metric.
344
+ `score` is what GRPO optimizes (it becomes the run's `total`). In standard (single-turn)
345
+ GRPO, each `RewardMetric` is averaged across scored completions and logged by name at
346
+ the managed heartbeat cadence, which is not guaranteed to be every optimizer step. That
347
+ is how the clean success rate becomes visible. Multi-turn scoring currently reports only
348
+ the scalar reward. Use the shaped `score` to confirm the model is learning *at all*, and
349
+ judge the run on the explicit `success` metric.
346
350
 
347
351
  When `thinking = true`, score the final answer unless you intentionally need the
348
352
  reasoning trace. Flash passes a string-compatible response object to `score_response`;
@@ -550,10 +554,9 @@ every run, the last two matter more the smaller the model:
550
554
  frontier one outright; a frontier `teacher_model` only earns its keep once the student is large
551
555
  enough to track it (~9B+). Early-stopping also largely neutralizes this gap, since the teacher-driven
552
556
  over-sharpening only compounds over many steps.
553
- - **Diagnose it in-band.** Watch the per-step **mean completion entropy** in the run's telemetry — a
554
- steady decline toward zero is the collapse happening. Confirm at serving by evaluating at
555
- **temperature=0** and flagging `finish_reason=length` completions that never emit your answer token,
556
- and compare an early checkpoint against the final one to watch the loop emerge over steps.
557
+ - **Diagnose it at serving.** Evaluate at **temperature=0** and flag
558
+ `finish_reason=length` completions that never emit your answer token. Compare an early checkpoint
559
+ against the final one to watch the loop emerge over steps.
557
560
 
558
561
  ### Distilling from base with no format anchor
559
562