freesolo-flash-dev 1.0.43__tar.gz → 1.0.45__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 (395) hide show
  1. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/PKG-INFO +1 -1
  2. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/cli/training_doc.py +33 -0
  3. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/opd_verl.py +69 -0
  4. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/opd_retry_contract.py +21 -1
  5. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/runner/__init__.py +17 -2
  6. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/pyproject.toml +2 -2
  7. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_multi_gpu_gate.py +21 -0
  8. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_opd_resume_safety.py +56 -0
  9. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_opd_verl.py +194 -2
  10. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/uv.lock +1 -1
  11. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/.dockerignore +0 -0
  12. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/.env.example +0 -0
  13. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  14. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  15. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  16. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  17. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/.github/dependabot.yml +0 -0
  18. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/.github/workflows/auto-rebake.yml +0 -0
  19. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/.github/workflows/bake-kernel-cache.yml +0 -0
  20. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/.github/workflows/ci.yml +0 -0
  21. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/.github/workflows/main-source-guard.yml +0 -0
  22. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/.github/workflows/notify-tests-repo.yml +0 -0
  23. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/.github/workflows/publish-dev.yml +0 -0
  24. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/.github/workflows/publish-image.yml +0 -0
  25. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/.github/workflows/publish.yml +0 -0
  26. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/.github/workflows/version-parity.yml +0 -0
  27. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/.github/workflows/worker-image.yml +0 -0
  28. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/.gitignore +0 -0
  29. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/CODE_OF_CONDUCT.md +0 -0
  30. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/CONTRIBUTING.md +0 -0
  31. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/Dockerfile +0 -0
  32. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/Dockerfile.worker +0 -0
  33. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/LICENSE +0 -0
  34. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/NOTICE +0 -0
  35. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/README.md +0 -0
  36. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/SECURITY.md +0 -0
  37. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/build/kernel_cache/.gitignore +0 -0
  38. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/build/kernel_cache/.keep +0 -0
  39. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/docker/Dockerfile.kernelcache +0 -0
  40. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/docker/Dockerfile.kernelcache.relayer +0 -0
  41. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/docker/bake_kernel_cache.py +0 -0
  42. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/docker/bake_pod_entry.py +0 -0
  43. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/docker/kernel_fingerprint.py +0 -0
  44. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/docker/make_rp_handler.py +0 -0
  45. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/__init__.py +0 -0
  46. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/_channel.py +0 -0
  47. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/_fileio.py +0 -0
  48. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/_logging.py +0 -0
  49. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/_update_check.py +0 -0
  50. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/adapter_artifacts.py +0 -0
  51. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/catalog.py +0 -0
  52. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/cli/__init__.py +0 -0
  53. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/cli/__main__.py +0 -0
  54. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/cli/_tty.py +0 -0
  55. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/cli/commands.py +0 -0
  56. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/cli/env_setup.py +0 -0
  57. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/cli/env_test.py +0 -0
  58. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/cli/envpush.py +0 -0
  59. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/cli/render.py +0 -0
  60. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/cli/traces.py +0 -0
  61. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/client/__init__.py +0 -0
  62. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/client/config.py +0 -0
  63. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/client/http.py +0 -0
  64. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/client/runtime_secrets.py +0 -0
  65. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/client/specs.py +0 -0
  66. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/cost/__init__.py +0 -0
  67. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/cost/analytical.py +0 -0
  68. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/cost/facts.py +0 -0
  69. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/cost/spec.py +0 -0
  70. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/cost/types.py +0 -0
  71. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/diagnostics.py +0 -0
  72. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/__init__.py +0 -0
  73. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/accounting.py +0 -0
  74. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/chalk_kernels.py +0 -0
  75. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/multiturn_reward_scoring.py +0 -0
  76. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/multiturn_rollout.py +0 -0
  77. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/recipe.py +0 -0
  78. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/steps.py +0 -0
  79. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/structured_outputs.py +0 -0
  80. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/vram.py +0 -0
  81. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/__init__.py +0 -0
  82. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/__main__.py +0 -0
  83. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/_pkg.py +0 -0
  84. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/adapter.py +0 -0
  85. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/decoding.py +0 -0
  86. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/finalize.py +0 -0
  87. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/gpu_setup.py +0 -0
  88. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/grpo.py +0 -0
  89. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/grpo_multimodal.py +0 -0
  90. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/grpo_perturn_trainer.py +0 -0
  91. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/heartbeat.py +0 -0
  92. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/hf.py +0 -0
  93. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/kernel_warmup.py +0 -0
  94. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/lora.py +0 -0
  95. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/opd.py +0 -0
  96. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/opd_gkd.py +0 -0
  97. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/opd_verl_multiturn.py +0 -0
  98. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/opd_verl_plugin.py +0 -0
  99. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/opd_verl_structured.py +0 -0
  100. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/opd_vllm.py +0 -0
  101. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/packing.py +0 -0
  102. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/perf/__init__.py +0 -0
  103. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/perf/attn.py +0 -0
  104. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/perf/diagnostics.py +0 -0
  105. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/perf/lifecycle.py +0 -0
  106. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/perf/liger.py +0 -0
  107. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/perf/loraplus.py +0 -0
  108. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/perf/memory.py +0 -0
  109. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/rl.py +0 -0
  110. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/rl_verl.py +0 -0
  111. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/rng.py +0 -0
  112. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/rollout_samples.py +0 -0
  113. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/sft.py +0 -0
  114. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/sft_verl.py +0 -0
  115. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/teacher.py +0 -0
  116. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/tokenizer_align.py +0 -0
  117. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/verl_common.py +0 -0
  118. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker/wandb_log.py +0 -0
  119. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/engine/worker_entrypoint.py +0 -0
  120. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/envs/__init__.py +0 -0
  121. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/envs/adapter.py +0 -0
  122. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/envs/archive.py +0 -0
  123. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/envs/archive_policy.py +0 -0
  124. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/envs/base.py +0 -0
  125. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/envs/loader.py +0 -0
  126. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/envs/pull.py +0 -0
  127. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/envs/registry.py +0 -0
  128. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/lora_rank.py +0 -0
  129. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/multimodal.py +0 -0
  130. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/opd_verl_validation.py +0 -0
  131. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/__init__.py +0 -0
  132. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/_auth.py +0 -0
  133. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/_deadline.py +0 -0
  134. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/_hf_artifacts.py +0 -0
  135. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/_hf_retry.py +0 -0
  136. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/_http.py +0 -0
  137. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/_instance.py +0 -0
  138. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/_instance_bootstrap.py +0 -0
  139. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/_instance_poll.py +0 -0
  140. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/_instance_provider.py +0 -0
  141. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/_poll.py +0 -0
  142. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/_worker.py +0 -0
  143. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/allocator.py +0 -0
  144. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/base.py +0 -0
  145. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/lambdalabs/__init__.py +0 -0
  146. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/lambdalabs/api.py +0 -0
  147. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/lambdalabs/auth.py +0 -0
  148. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/lambdalabs/gpus.py +0 -0
  149. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/lambdalabs/jobs/__init__.py +0 -0
  150. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/lambdalabs/jobs/builders.py +0 -0
  151. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/lambdalabs/preflight.py +0 -0
  152. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/lambdalabs/pricing.py +0 -0
  153. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/preflight.py +0 -0
  154. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/realized.py +0 -0
  155. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/runpod/__init__.py +0 -0
  156. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/runpod/api.py +0 -0
  157. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/runpod/auth.py +0 -0
  158. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/runpod/cost.py +0 -0
  159. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/runpod/gpus.py +0 -0
  160. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/runpod/jobs.py +0 -0
  161. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/runpod/keys.py +0 -0
  162. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/runpod/preflight.py +0 -0
  163. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/runpod/pricing.py +0 -0
  164. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/runpod/slots.py +0 -0
  165. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/runpod/train/__init__.py +0 -0
  166. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/runpod/train/endpoints.py +0 -0
  167. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/vast/__init__.py +0 -0
  168. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/vast/api.py +0 -0
  169. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/vast/auth.py +0 -0
  170. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/vast/jobs/__init__.py +0 -0
  171. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/vast/jobs/builders.py +0 -0
  172. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/vast/preflight.py +0 -0
  173. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/vast/pricing.py +0 -0
  174. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/providers/weight_cache_preload.py +0 -0
  175. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/py.typed +0 -0
  176. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/runner/checkpoints.py +0 -0
  177. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/runner/deploy.py +0 -0
  178. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/runner/lifecycle.py +0 -0
  179. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/runner/verified_revisions.py +0 -0
  180. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/schema/__init__.py +0 -0
  181. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/schema/fields.py +0 -0
  182. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/serve/__init__.py +0 -0
  183. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/serve/deploy.py +0 -0
  184. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/serve/export.py +0 -0
  185. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/serve/preflight.py +0 -0
  186. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/serve/pricing.py +0 -0
  187. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/serve/urls.py +0 -0
  188. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/server/__init__.py +0 -0
  189. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/server/__main__.py +0 -0
  190. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/server/_deps.py +0 -0
  191. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/server/_internal_client.py +0 -0
  192. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/server/_locks.py +0 -0
  193. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/server/_runtime.py +0 -0
  194. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/server/app.py +0 -0
  195. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/server/auth.py +0 -0
  196. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/server/billing.py +0 -0
  197. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/server/billing_retry.py +0 -0
  198. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/server/checkpoints.py +0 -0
  199. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/server/db.py +0 -0
  200. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/server/environment_registry.py +0 -0
  201. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/server/envs.py +0 -0
  202. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/server/projects.py +0 -0
  203. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/server/reconcile.py +0 -0
  204. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/server/repo_cleanup.py +0 -0
  205. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/server/routes/__init__.py +0 -0
  206. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/server/routes/envs.py +0 -0
  207. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/server/routes/meta.py +0 -0
  208. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/server/routes/runs.py +0 -0
  209. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/server/routes/serving.py +0 -0
  210. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/server/run_registry.py +0 -0
  211. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/flash/spec.py +0 -0
  212. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/infisical-entrypoint.sh +0 -0
  213. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/scripts/build_dev_dist.py +0 -0
  214. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/scripts/scrub_history.sh +0 -0
  215. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/__init__.py +0 -0
  216. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/_helpers/__init__.py +0 -0
  217. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/_helpers/runner.py +0 -0
  218. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/_helpers/specs.py +0 -0
  219. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/_helpers/vast.py +0 -0
  220. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/conftest.py +0 -0
  221. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/fixtures/math_eval.jsonl +0 -0
  222. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/fixtures/math_train.jsonl +0 -0
  223. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/live/__init__.py +0 -0
  224. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/live/conftest.py +0 -0
  225. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/live/test_lambda_live.py +0 -0
  226. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/live/test_runpod_live.py +0 -0
  227. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/live/test_vast_live.py +0 -0
  228. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_accounting_coverage.py +0 -0
  229. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_agent_flash_cli_contract.py +0 -0
  230. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_algorithms.py +0 -0
  231. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_allocator.py +0 -0
  232. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_async_uploads.py +0 -0
  233. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_b200_rollout_opt.py +0 -0
  234. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_backend_jobspec_contract.py +0 -0
  235. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_base_model_provenance.py +0 -0
  236. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_billing_retry.py +0 -0
  237. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_cancel_remote.py +0 -0
  238. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_catalog_consistency.py +0 -0
  239. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_chalk_kernels.py +0 -0
  240. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_charge_pricing.py +0 -0
  241. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_checkpoints.py +0 -0
  242. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_cli_commands.py +0 -0
  243. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_cli_commands_coverage.py +0 -0
  244. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_cli_errors.py +0 -0
  245. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_cli_estimate.py +0 -0
  246. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_cli_help.py +0 -0
  247. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_cli_main_coverage.py +0 -0
  248. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_cli_managed.py +0 -0
  249. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_cli_render_coverage.py +0 -0
  250. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_cli_render_theme.py +0 -0
  251. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_cli_traces.py +0 -0
  252. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_client.py +0 -0
  253. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_client_server_integration.py +0 -0
  254. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_client_stream_reads.py +0 -0
  255. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_config_overrides.py +0 -0
  256. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_controlled_experiment_repairs.py +0 -0
  257. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_cost_analytical.py +0 -0
  258. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_cost_equation.py +0 -0
  259. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_cost_estimate.py +0 -0
  260. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_cost_hardware.py +0 -0
  261. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_cost_models.py +0 -0
  262. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_cost_rewards.py +0 -0
  263. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_deploy_latency.py +0 -0
  264. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_dev_channel.py +0 -0
  265. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_disk_gb.py +0 -0
  266. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_endpoint_name.py +0 -0
  267. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_env_cache_evict.py +0 -0
  268. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_env_delete.py +0 -0
  269. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_env_download.py +0 -0
  270. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_env_download_api.py +0 -0
  271. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_env_no_install.py +0 -0
  272. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_env_publish.py +0 -0
  273. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_env_pull.py +0 -0
  274. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_env_pull_coverage.py +0 -0
  275. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_env_pull_loader_coverage.py +0 -0
  276. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_env_pull_managed_control_plane.py +0 -0
  277. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_env_push.py +0 -0
  278. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_env_rate_limit_resolve.py +0 -0
  279. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_env_test.py +0 -0
  280. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_envs_coverage.py +0 -0
  281. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_export.py +0 -0
  282. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_flash_mvp.py +0 -0
  283. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_flash_worker.py +0 -0
  284. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_flashinfer_cache_dirs.py +0 -0
  285. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_github_urlopen_retry.py +0 -0
  286. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_gpu_type_single_field.py +0 -0
  287. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_gpus.py +0 -0
  288. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_grpo_cudagraphs.py +0 -0
  289. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_grpo_mask_aware.py +0 -0
  290. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_grpo_params.py +0 -0
  291. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_grpo_perturn.py +0 -0
  292. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_grpo_sleep_gate.py +0 -0
  293. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_health_liveness.py +0 -0
  294. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_hf_retry.py +0 -0
  295. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_idle_endpoint_reaper.py +0 -0
  296. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_instance_bootstrap_coverage.py +0 -0
  297. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_internal_client.py +0 -0
  298. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_jobs.py +0 -0
  299. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_kernel_cache.py +0 -0
  300. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_kernel_fingerprint.py +0 -0
  301. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_kv_util.py +0 -0
  302. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_lambda_api_coverage.py +0 -0
  303. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_lambda_runner.py +0 -0
  304. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_logging.py +0 -0
  305. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_login_perms.py +0 -0
  306. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_lora_rank_coverage.py +0 -0
  307. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_lora_rank_preflight.py +0 -0
  308. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_managed_config_fields.py +0 -0
  309. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_managed_hf_repo.py +0 -0
  310. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_metrics_schema_agent_contract.py +0 -0
  311. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_mig_guard.py +0 -0
  312. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_multimodal_input_grads.py +0 -0
  313. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_multimodal_training.py +0 -0
  314. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_multiturn_image_rollout.py +0 -0
  315. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_multiturn_per_turn_reward.py +0 -0
  316. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_multiturn_rollout.py +0 -0
  317. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_multiturn_rollout_coverage.py +0 -0
  318. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_multiturn_rollout_records.py +0 -0
  319. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_multiturn_rollout_request_policy.py +0 -0
  320. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_named_reward_metrics.py +0 -0
  321. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_oom_escalate_gpu.py +0 -0
  322. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_opd.py +0 -0
  323. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_opd_full_state_resume.py +0 -0
  324. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_opd_vllm.py +0 -0
  325. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_open_model_policy.py +0 -0
  326. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_orchestrator_flash.py +0 -0
  327. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_packing.py +0 -0
  328. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_packing_coverage.py +0 -0
  329. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_poll_helpers.py +0 -0
  330. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_preflight.py +0 -0
  331. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_projects.py +0 -0
  332. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_provider_preflight_coverage.py +0 -0
  333. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_provider_routing.py +0 -0
  334. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_provider_teardown_robustness.py +0 -0
  335. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_providers_symmetry.py +0 -0
  336. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_realized_cost.py +0 -0
  337. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_repo_cleanup.py +0 -0
  338. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_resolve_params_b.py +0 -0
  339. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_resume_on_retry.py +0 -0
  340. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_rl_verl.py +0 -0
  341. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_rollout_samples.py +0 -0
  342. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_runmgmt.py +0 -0
  343. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_runpod_api_delete.py +0 -0
  344. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_runpod_key_fingerprint.py +0 -0
  345. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_runpod_key_waterfall.py +0 -0
  346. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_runpod_slots.py +0 -0
  347. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_serve.py +0 -0
  348. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_server_api.py +0 -0
  349. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_server_app_coverage.py +0 -0
  350. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_server_auth_singleflight.py +0 -0
  351. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_server_billing.py +0 -0
  352. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_server_checkpoints_coverage.py +0 -0
  353. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_server_db.py +0 -0
  354. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_server_envs_coverage.py +0 -0
  355. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_server_locks_coverage.py +0 -0
  356. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_serving_context_preflight.py +0 -0
  357. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_serving_contract.py +0 -0
  358. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_serving_pricing_coverage.py +0 -0
  359. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_serving_schema_coverage.py +0 -0
  360. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_sft_activation_shaping.py +0 -0
  361. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_sft_chunked_nll.py +0 -0
  362. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_sft_example_selection.py +0 -0
  363. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_sft_gc_off.py +0 -0
  364. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_sft_max_context.py +0 -0
  365. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_sft_tokenize_cache.py +0 -0
  366. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_sft_verl.py +0 -0
  367. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_single_turn_multimodal_grpo.py +0 -0
  368. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_spec_and_validation.py +0 -0
  369. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_structured_outputs.py +0 -0
  370. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_thinking_config.py +0 -0
  371. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_train_context_preflight.py +0 -0
  372. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_training_controls.py +0 -0
  373. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_update_check.py +0 -0
  374. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_vast_api.py +0 -0
  375. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_vast_api_coverage.py +0 -0
  376. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_vast_offers.py +0 -0
  377. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_vast_provider_coverage.py +0 -0
  378. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_vast_runner.py +0 -0
  379. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_verified_revisions.py +0 -0
  380. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_verifiers.py +0 -0
  381. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_verl_common.py +0 -0
  382. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_version.py +0 -0
  383. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_vl_warmstart_adapter_keys.py +0 -0
  384. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_vram_architecture.py +0 -0
  385. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_wandb_log_coverage.py +0 -0
  386. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_wandb_naming.py +0 -0
  387. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_warmstart_adapter_download.py +0 -0
  388. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_warmstart_cross_repo.py +0 -0
  389. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_weight_cache.py +0 -0
  390. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_worker_dryrun.py +0 -0
  391. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_worker_hardexit.py +0 -0
  392. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_worker_image.py +0 -0
  393. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_worker_init_heartbeat.py +0 -0
  394. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_worker_stack.py +0 -0
  395. {freesolo_flash_dev-1.0.43 → freesolo_flash_dev-1.0.45}/tests/test_worker_thinking.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: freesolo-flash-dev
3
- Version: 1.0.43
3
+ Version: 1.0.45
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
@@ -697,6 +697,39 @@ on a beyond-noise improvement.
697
697
 
698
698
  ---
699
699
 
700
+ ## Multi-GPU training (`gpu.count > 1`)
701
+
702
+ Flash trains on one card by default. The default `trl` backend is single-process and never
703
+ reads `gpu.count`, so raising the count alone would rent n cards and train on one — submit
704
+ rejects that rather than billing for idle hardware.
705
+
706
+ To shard one job across several cards, select the `verl` backend for the phase you are running:
707
+
708
+ ```toml
709
+ [gpu]
710
+ type = "B200"
711
+ count = 4
712
+ # provider must also be pinned: not every provider puts all n cards on ONE machine, and
713
+ # submit names the ones that qualify when it rejects an unpinned multi-gpu spec.
714
+
715
+ [worker_env]
716
+ FLASH_SFT_BACKEND = "verl" # SFT
717
+ # FLASH_RL_BACKEND = "verl" # GRPO (the key is RL, not GRPO)
718
+ # FLASH_OPD_BACKEND = "verl" # OPD
719
+ ```
720
+
721
+ One key per phase, and GRPO's is `FLASH_RL_BACKEND`. The verl worker launches one rank per
722
+ card with Ulysses sequence parallelism.
723
+
724
+ The verl GRPO backend covers single-turn, non-tool, non-multimodal runs; multi-turn, tool, and
725
+ image-prompt environments stay on `trl` (which is single-GPU). Warm-starting with
726
+ `init_from_adapter` while `kl_penalty_coef > 0` is also `trl`-only: verl computes its KL
727
+ reference with adapters disabled, so the reference would be the bare base model instead of your
728
+ SFT adapter, pulling the policy back toward base. Every one of these raises at startup rather
729
+ than quietly training on a different contract.
730
+
731
+ ---
732
+
700
733
  ## Command reference
701
734
 
702
735
  ```bash
@@ -86,6 +86,20 @@ _TEXT_TEACHER_SHUTDOWN_WAIT_S = 5.0
86
86
  _TEXT_TEACHER_REQUEST_BACKLOG = 64
87
87
 
88
88
 
89
+ def _align_granularity(groups, student_tokens) -> float:
90
+ """mean student-tokens-per-group: the alignment-health signal coverage cannot provide.
91
+
92
+ a degenerate alignment that collapses every student token onto a single group still scores
93
+ coverage ~1.0, so coverage alone never flags it. this is the trl opd formula verbatim
94
+ (``opd.py``): non-empty student spans over surviving groups, counted after fully-forced groups
95
+ are dropped so the ratio describes the signal that actually reaches the loss.
96
+ """
97
+ if not groups:
98
+ return 0.0
99
+ n_align = sum(1 for st in student_tokens if st.end > st.start)
100
+ return n_align / len(groups)
101
+
102
+
89
103
  class _TeacherBridgeHTTPServer(ThreadingHTTPServer):
90
104
  request_queue_size = _TEXT_TEACHER_REQUEST_BACKLOG
91
105
 
@@ -518,6 +532,12 @@ class _TeacherAlignmentBridge:
518
532
  self.forced_tokens = int(state.get("forced_tokens", 0))
519
533
  self.dropped_forced_groups = int(state.get("dropped_forced_groups", 0))
520
534
  self.coverage_sum = float(state.get("coverage_sum", state.get("granularity_sum", 0.0)))
535
+ # alignment GRANULARITY (mean aligned-groups-per-sequence), distinct from coverage: a
536
+ # collapsed alignment that maps every student token onto one group still scores coverage
537
+ # ~1.0, so coverage alone cannot flag that failure mode. no legacy alias here -- the old
538
+ # granularity_* state keys held coverage, so reading them would restore the wrong quantity.
539
+ self.align_group_sum = float(state.get("align_group_sum", 0.0))
540
+ self.align_group_n = int(state.get("align_group_n", 0))
521
541
  # resume: baseline the per-step delta counters at the restored cumulative mass, so the
522
542
  # first resumed step reports its own coverage instead of the whole prior run's.
523
543
  self._prev_aligned = self.aligned_sequences
@@ -648,6 +668,8 @@ class _TeacherAlignmentBridge:
648
668
  "aligned_sequences": self.aligned_sequences,
649
669
  "empty_alignments": self.empty_alignments,
650
670
  "coverage_sum": self.coverage_sum,
671
+ "align_group_sum": self.align_group_sum,
672
+ "align_group_n": self.align_group_n,
651
673
  }
652
674
 
653
675
  def _empty(self, prompt_length: int, response_length: int) -> dict:
@@ -756,12 +778,15 @@ class _TeacherAlignmentBridge:
756
778
  aligned_group_count = len(groups)
757
779
  groups = _drop_fully_forced_groups(groups, kept_forced)
758
780
  coverage = groupwise_coverage(groups, student_tokens)
781
+ granularity = _align_granularity(groups, student_tokens)
759
782
  with self._stats_lock:
760
783
  self.teacher_input_tokens += teacher_input_tokens
761
784
  self.dropped_forced_groups += aligned_group_count - len(groups)
762
785
  self.coverage_sum += coverage
763
786
  if groups:
764
787
  self.aligned_sequences += 1
788
+ self.align_group_sum += granularity
789
+ self.align_group_n += 1
765
790
  else:
766
791
  self.empty_alignments += 1
767
792
  teacher_ids, teacher_logprobs = encode_shifted_group_metadata(
@@ -1084,11 +1109,14 @@ class _TeacherAlignmentBridge:
1084
1109
  groups = groupwise_alignment(student_tokens, teacher_tokens)
1085
1110
  groups = [(indices, logsum) for indices, logsum in groups if indices]
1086
1111
  coverage = groupwise_coverage(groups, student_tokens)
1112
+ granularity = _align_granularity(groups, student_tokens)
1087
1113
  with self._stats_lock:
1088
1114
  self.teacher_input_tokens += len(turn["prompt_ids"]) + len(student_ids)
1089
1115
  self.coverage_sum += coverage
1090
1116
  if groups:
1091
1117
  self.aligned_sequences += 1
1118
+ self.align_group_sum += granularity
1119
+ self.align_group_n += 1
1092
1120
  else:
1093
1121
  self.empty_alignments += 1
1094
1122
  teacher_ids, teacher_logprobs = encode_shifted_group_metadata(
@@ -2515,6 +2543,17 @@ def run_opd_verl(spec=None) -> None:
2515
2543
  "verl OPD produced no distillation-loss metrics for the whole run — the "
2516
2544
  "distillation path never engaged; refusing to publish"
2517
2545
  )
2546
+ if len(final_accounting["loss_curve"]) != final_step:
2547
+ # record_step only checks that each metric line FOLLOWS the last one, so a missing
2548
+ # trailing metric (on_line skips any step-tagged line whose loss it cannot parse) leaves
2549
+ # a curve shorter than the checkpoint verl actually wrote, and nothing later arrives to
2550
+ # catch it. opt_steps is published from this curve, so a short curve would understate the
2551
+ # updates applied. fail loud instead of reporting a number the curve cannot support.
2552
+ raise RuntimeError(
2553
+ f"verl OPD recorded {len(final_accounting['loss_curve'])} distillation-loss metrics "
2554
+ f"for {final_step} optimizer updates; refusing to publish an accounting that does "
2555
+ "not cover every update"
2556
+ )
2518
2557
  if int(final_accounting.get("aligned_sequences", 0) or 0) <= 0:
2519
2558
  # zeroed-mask pass-through batches still emit a (zero) loss metric, so the loss-curve
2520
2559
  # check alone cannot distinguish real distillation from a run where the teacher never
@@ -2553,6 +2592,10 @@ def run_opd_verl(spec=None) -> None:
2553
2592
  generated_tokens=int(final_accounting["generated_tokens"]),
2554
2593
  notes={
2555
2594
  "steps": update_horizon,
2595
+ # optimizer updates that actually produced a distillation loss. record_step enforces
2596
+ # loss_curve length == the metric step, and the guard above rejects a curve shorter
2597
+ # than final_step, so this is measured, not assumed.
2598
+ "opt_steps": len(final_accounting["loss_curve"]),
2556
2599
  "epochs": knobs.epochs,
2557
2600
  "retained_prompts": len(prompts),
2558
2601
  "dropped_long_prompts": dropped_long,
@@ -2568,6 +2611,15 @@ def run_opd_verl(spec=None) -> None:
2568
2611
  if final_accounting["aligned_sequences"]
2569
2612
  else 0.0
2570
2613
  ),
2614
+ # the real alignment-health signal. mean_coverage reads ~1.0 even when the alignment
2615
+ # has collapsed every student token onto one group, so it cannot flag that failure
2616
+ # mode on its own; this ratio can.
2617
+ "mean_align_granularity": (
2618
+ float(final_accounting["align_group_sum"])
2619
+ / int(final_accounting["align_group_n"])
2620
+ if final_accounting["align_group_n"]
2621
+ else 0.0
2622
+ ),
2571
2623
  "truncated_rollouts": int(final_accounting["truncated_rollouts"]),
2572
2624
  "forced_tokens": int(final_accounting["forced_tokens"]),
2573
2625
  "dropped_forced_groups": int(
@@ -2591,6 +2643,23 @@ def run_opd_verl(spec=None) -> None:
2591
2643
  if multi_turn and final_accounting["episodes_seen"]
2592
2644
  else None
2593
2645
  ),
2646
+ # the engine length actually handed to vllm (prompt + completion), already clamped to
2647
+ # the model's own limit. the prompt filter is carved out of this same number.
2648
+ "vllm_max_model_len": max_model_len,
2649
+ # teacher call shape. only the single-turn TEXT path goes through the batcher, which
2650
+ # holds a fixed cap and one serial scoring thread. the multimodal path scores one
2651
+ # item per call and the multi-turn path batches a whole episode, and both run on the
2652
+ # bridge's own request threads -- neither has a constant, so None records "not
2653
+ # batched by flash" instead of asserting a number nothing enforces.
2654
+ # the cap is bounded by the samples one step can actually produce (trl does the same
2655
+ # in _opd_teacher_batch_size): a step of 1 rollout can never fill a batch of 8, and
2656
+ # reporting the global cap there would describe a shape the run cannot reach.
2657
+ "opd_teacher_batch_size": (
2658
+ min(_TEXT_TEACHER_BATCH_SIZE, max(1, prompts_per_step * knobs.group_size))
2659
+ if not multimodal and not multi_turn
2660
+ else None
2661
+ ),
2662
+ "opd_teacher_workers": 1 if not multimodal and not multi_turn else None,
2594
2663
  "rollout_backend": "verl_vllm",
2595
2664
  "verl_version": "0.8.0",
2596
2665
  "verl_backend": "fsdp",
@@ -17,7 +17,12 @@ OPD_OPTIMIZER_START_PHASE = "opd"
17
17
  OPD_RETRY_ARTIFACT_PREFIX = "_opd_retry"
18
18
  OPD_OPTIMIZER_START_FILENAME = "optimizer-start.v1.json"
19
19
  OPD_RESUME_REVISION_ENV = "FLASH_OPD_RESUME_REVISION"
20
- OPD_RESUME_STATE_VERSION = 2
20
+ # bumped to 3 when the verl worker began accumulating align_group_sum/align_group_n. a version-2
21
+ # state carries neither, so resuming one would restart the accumulator at zero and publish a
22
+ # post-resume-only mean_align_granularity as though it described the whole run. the version check
23
+ # below already rejects mismatches fail-closed, so the bump refuses those states outright instead of
24
+ # threading a completeness flag through the accounting.
25
+ OPD_RESUME_STATE_VERSION = 3
21
26
  MAX_BOUNDED_NONNEGATIVE_INTEGER = (1 << 63) - 1
22
27
 
23
28
  # a full-state opd resume checkpoint (the custom loop's counterpart to the hf trainer's checkpoint-n)
@@ -55,6 +60,13 @@ _OPD_RESUME_ACCOUNTING_SCHEMA = {
55
60
  "opd_phase_seconds": "dict",
56
61
  "opd_phase_counts": "dict",
57
62
  }
63
+ # alignment granularity, accumulated only by the verl worker. the trl worker's granularity lives in
64
+ # the granularity_* slots above; in verl those same slots hold COVERAGE, so verl carries its own pair
65
+ # and both must be validated when present rather than coerced past the fail-closed contract.
66
+ _OPD_RESUME_OPTIONAL_ACCOUNTING_SCHEMA = {
67
+ "align_group_sum": "nonneg_number",
68
+ "align_group_n": "nonneg_int",
69
+ }
58
70
 
59
71
 
60
72
  def bounded_nonnegative_integer(value: object, *, field: str) -> int:
@@ -136,6 +148,14 @@ def validate_opd_resume_state_metadata(
136
148
  elif field_type == "dict" and not isinstance(value, dict):
137
149
  raise ValueError(f"opd resume state {field} must be an object")
138
150
 
151
+ for field, field_type in _OPD_RESUME_OPTIONAL_ACCOUNTING_SCHEMA.items():
152
+ if field not in state:
153
+ continue
154
+ if field_type == "nonneg_int":
155
+ _nonnegative_integer(state[field], field=field)
156
+ else:
157
+ _finite_number(state[field], field=field, nonnegative=True)
158
+
139
159
  for field in ("loss_curve", "coverage_curve"):
140
160
  curve = state[field]
141
161
  if len(curve) != opt_steps:
@@ -1187,6 +1187,15 @@ _MULTI_GPU_BACKENDS: frozenset[str] = frozenset({"verl"})
1187
1187
  _MULTI_GPU_PROVIDERS: frozenset[str] = frozenset({"runpod"})
1188
1188
 
1189
1189
 
1190
+ def _backend_env_key(spec: JobSpec) -> str:
1191
+ """the [worker_env] key that selects this spec's training backend.
1192
+
1193
+ one definition so the gate's remedy cannot name a different key than the resolver reads: the
1194
+ phase mapping is not derivable from the algorithm (grpo -> FLASH_RL_BACKEND).
1195
+ """
1196
+ return f"FLASH_{spec.phase.upper()}_BACKEND"
1197
+
1198
+
1190
1199
  def _effective_backend(spec: JobSpec) -> str:
1191
1200
  """the training backend this spec will actually run, as the worker resolves it.
1192
1201
 
@@ -1195,7 +1204,7 @@ def _effective_backend(spec: JobSpec) -> str:
1195
1204
  empty dict and forwards a fixed credential allowlist, never the ambient control-plane env. so the
1196
1205
  backend is a property of the SPEC and the gate can read it here rather than guessing.
1197
1206
  """
1198
- key = f"FLASH_{spec.phase.upper()}_BACKEND"
1207
+ key = _backend_env_key(spec)
1199
1208
  worker_env = getattr(spec, "worker_env", None) or {}
1200
1209
  for name, value in worker_env.items():
1201
1210
  if str(name).upper() == key:
@@ -1215,10 +1224,16 @@ def _require_supported_gpu_count(spec: JobSpec) -> None:
1215
1224
  return
1216
1225
  backend = _effective_backend(spec)
1217
1226
  if backend not in _MULTI_GPU_BACKENDS:
1227
+ # name the key that actually enables sharding. the backend is selected only through
1228
+ # [worker_env], which no other error or doc mentions, so a message that offers just
1229
+ # "set gpu.count to 1" leaves multi-gpu undiscoverable.
1230
+ key = _backend_env_key(spec)
1231
+ sharding = ", ".join(sorted(_MULTI_GPU_BACKENDS))
1218
1232
  raise ValueError(
1219
1233
  f"multi-gpu training (gpu.count={count}) is not supported by the {backend!r} backend "
1220
1234
  f"for algorithm {spec.algorithm!r}; it trains in a single process and would leave "
1221
- f"{count - 1} rented card(s) idle. set gpu.count to 1"
1235
+ f"{count - 1} rented card(s) idle. set gpu.count to 1, or select a sharding backend "
1236
+ f'with [worker_env] {key} = "{sharding}"'
1222
1237
  )
1223
1238
  provider = (getattr(spec.gpu, "provider", "") or "").strip().lower()
1224
1239
  if provider not in _MULTI_GPU_PROVIDERS:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "freesolo-flash-dev"
3
- version = "1.0.43"
3
+ version = "1.0.45"
4
4
  description = "Flash — managed LoRA post-training (SFT/GRPO/OPD) for Freesolo environments, driven by the `flash` CLI"
5
5
  readme = "README.md"
6
6
  # RunPod Flash supports Python 3.11-3.12 (not 3.13 yet).
@@ -116,7 +116,7 @@ artifacts = ["flash/py.typed"]
116
116
  # auto-published by .github/workflows/publish-dev.yml: a push to `dev` whose version isn't yet on
117
117
  # PyPI publishes it; pushes that don't change it find it already published and no-op. So cutting a
118
118
  # dev-channel release = bump this (and [project].version) and merge to `dev`.
119
- version = "1.0.43"
119
+ version = "1.0.45"
120
120
 
121
121
  [tool.pytest.ini_options]
122
122
  pythonpath = ["."]
@@ -110,3 +110,24 @@ def test_run_training_gates_effective_spec_on_recovery():
110
110
  # before any provisioning (the gate precedes the first get_status/allocation touch).
111
111
  with pytest.raises(ValueError, match="multi-gpu training"):
112
112
  _run_training(_spec(2), io.StringIO(), prior_cost=0.0)
113
+
114
+
115
+ @pytest.mark.parametrize(
116
+ ("algorithm", "key"),
117
+ [("sft", "FLASH_SFT_BACKEND"), ("grpo", "FLASH_RL_BACKEND"), ("opd", "FLASH_OPD_BACKEND")],
118
+ )
119
+ def test_gate_message_names_the_key_that_enables_sharding(algorithm, key):
120
+ from flash import runner
121
+
122
+ # [worker_env] is the ONLY route to a sharding backend, and the phase key is not guessable from
123
+ # the algorithm (grpo -> FLASH_RL_BACKEND). a message offering only "set gpu.count to 1" leaves
124
+ # multi-gpu undiscoverable, so assert the remedy travels with the rejection.
125
+ # match the backend rejection specifically: the gate also raises for gpu.provider, and that
126
+ # path carries no backend key, so a bare raises() would pass while checking nothing.
127
+ with pytest.raises(ValueError, match="multi-gpu training") as excinfo:
128
+ runner._require_supported_gpu_count(_spec(4, algorithm))
129
+
130
+ message = str(excinfo.value)
131
+ assert key in message
132
+ assert "verl" in message
133
+ assert "worker_env" in message
@@ -1123,6 +1123,62 @@ def test_gate_blocks_pinned_checkpoint_metadata_download_failure(monkeypatch, tm
1123
1123
  _run_gate()
1124
1124
 
1125
1125
 
1126
+ @pytest.mark.parametrize(
1127
+ "overrides",
1128
+ [
1129
+ {"align_group_sum": float("nan")},
1130
+ {"align_group_sum": float("inf")},
1131
+ {"align_group_sum": -1.0},
1132
+ {"align_group_n": -1},
1133
+ {"align_group_n": 1.5},
1134
+ {"align_group_n": True},
1135
+ ],
1136
+ )
1137
+ def test_resume_validator_rejects_corrupt_alignment_granularity(overrides):
1138
+ # the verl worker resumes its granularity accumulators from these fields and publishes their
1139
+ # ratio into train_meta. a tampered or corrupt checkpoint must fail the fail-closed contract
1140
+ # here, not coerce past it and propagate a non-finite or negative mean into every later
1141
+ # checkpoint and the run's reported alignment health.
1142
+ state = _valid_resume_state(2, **overrides)
1143
+ # match on the corrupt field itself: a validator that rejected for some unrelated reason would
1144
+ # pass a bare raises() while leaving this pair entirely unchecked.
1145
+ (field,) = overrides
1146
+
1147
+ with pytest.raises(ValueError, match=field):
1148
+ validate_opd_resume_state_metadata(state, expected_seed=42, checkpoint_step=2)
1149
+
1150
+
1151
+ def test_resume_state_version_rejects_states_predating_alignment_granularity():
1152
+ # the verl worker resumes align_group_sum/align_group_n and publishes their ratio as
1153
+ # mean_align_granularity. a state written before those accumulators existed carries neither, so
1154
+ # resuming one restarts the accumulator at zero and the ratio would describe only the
1155
+ # post-resume samples while being reported as the whole run's alignment health. the contract
1156
+ # version is the enforcement point: it must have moved past 2 so the existing fail-closed check
1157
+ # refuses those states outright.
1158
+ assert OPD_RESUME_STATE_VERSION > 2
1159
+
1160
+ stale = _valid_resume_state(2)
1161
+ stale["contract_version"] = 2
1162
+
1163
+ with pytest.raises(ValueError, match="contract_version"):
1164
+ validate_opd_resume_state_metadata(stale, expected_seed=42, checkpoint_step=2)
1165
+
1166
+
1167
+ def test_resume_validator_accepts_alignment_granularity_and_states_without_it():
1168
+ # verl writes the pair; trl does not. both must validate, so the fields are checked when present
1169
+ # rather than required -- otherwise adding them would reject every trl checkpoint.
1170
+ with_granularity = _valid_resume_state(2, align_group_sum=3.0, align_group_n=2)
1171
+ validated = validate_opd_resume_state_metadata(
1172
+ with_granularity, expected_seed=42, checkpoint_step=2
1173
+ )
1174
+ assert validated["align_group_sum"] == 3.0
1175
+ assert validated["align_group_n"] == 2
1176
+
1177
+ without = _valid_resume_state(2)
1178
+ assert "align_group_sum" not in without
1179
+ validate_opd_resume_state_metadata(without, expected_seed=42, checkpoint_step=2)
1180
+
1181
+
1126
1182
  def test_shared_resume_metadata_validator_returns_a_copy():
1127
1183
  state = _valid_resume_state(3)
1128
1184
 
@@ -64,6 +64,7 @@ from flash.engine.worker.opd_verl_structured import (
64
64
  canonical_structured_spec,
65
65
  )
66
66
  from flash.engine.worker.tokenizer_align import TeacherToken
67
+ from flash.opd_retry_contract import OPD_RESUME_STATE_VERSION
67
68
  from flash.opd_verl_validation import validate_opd_verl_structured_outputs
68
69
 
69
70
 
@@ -719,7 +720,7 @@ class _BridgeTokenizer:
719
720
 
720
721
  def _resume_accounting(step=2):
721
722
  return {
722
- "contract_version": 2,
723
+ "contract_version": OPD_RESUME_STATE_VERSION,
723
724
  "seed": 42,
724
725
  "opt_steps": step,
725
726
  "step": step,
@@ -841,7 +842,14 @@ def test_write_train_meta_integrates_canonical_failure_accounting_metadata():
841
842
  assert "**_failure_accounting_metadata(final_accounting)" in write_train_meta_source
842
843
  assert '"teacher_transient":' not in write_train_meta_source
843
844
  assert '"teacher_error":' not in write_train_meta_source
844
- assert '"mean_align_granularity":' not in write_train_meta_source
845
+ # granularity IS reported, but only from its own accumulator. the snapshot's granularity_sum /
846
+ # granularity_n are legacy aliases holding COVERAGE, so deriving the ratio from them would
847
+ # publish coverage a second time under the name of the one signal coverage cannot provide.
848
+ assert '"mean_align_granularity":' in write_train_meta_source
849
+ assert 'final_accounting["align_group_sum"]' in write_train_meta_source
850
+ assert 'final_accounting["align_group_n"]' in write_train_meta_source
851
+ assert "granularity_sum" not in write_train_meta_source
852
+ assert "granularity_n" not in write_train_meta_source
845
853
 
846
854
 
847
855
  class _BridgeTeacher:
@@ -1051,6 +1059,87 @@ def test_bridge_verifies_prompt_and_serializes_aligned_native_fields():
1051
1059
  bridge.score(0, 2, [10, 12, 65, 99])
1052
1060
 
1053
1061
 
1062
+ def test_bridge_granularity_separates_a_collapsed_alignment_from_a_healthy_one():
1063
+ # both teachers cover every student token, so coverage is 1.0 for BOTH and cannot tell them
1064
+ # apart. the merged teacher returns one span where the healthy one returns two, which is the
1065
+ # degenerate collapse mean_coverage is blind to -- granularity is the signal that sees it.
1066
+ healthy = _text_bridge(_BridgeTeacher())
1067
+ healthy.score(0, 2, [10, 11, 65, 66, 99])
1068
+ collapsed = _text_bridge(_MergedBridgeTeacher())
1069
+ collapsed.score(0, 2, [10, 11, 65, 66, 99])
1070
+
1071
+ assert healthy.coverage_sum == collapsed.coverage_sum == 1.0
1072
+ assert healthy.aligned_sequences == collapsed.aligned_sequences == 1
1073
+ # two student tokens over two groups vs two student tokens over one merged group.
1074
+ assert healthy.align_group_sum == 1.0
1075
+ assert collapsed.align_group_sum == 2.0
1076
+ assert healthy.align_group_n == collapsed.align_group_n == 1
1077
+
1078
+
1079
+ def test_bridge_granularity_counts_only_sequences_that_reached_the_loss():
1080
+ # a teacher that returns no tokens still reaches the accounting, but its sequence carries no
1081
+ # groups and contributes nothing to the loss. folding its 0.0 into the mean would report a
1082
+ # collapse that never happened -- it belongs in empty_alignments only.
1083
+ class _EmptyAlignmentTeacher:
1084
+ def score(self, _prompt_text, _completion_text):
1085
+ return []
1086
+
1087
+ bridge = _text_bridge(_BridgeTeacher())
1088
+ bridge.score(0, 2, [10, 11, 65, 66, 99])
1089
+ assert bridge.align_group_n == 1
1090
+ assert bridge.align_group_sum == 1.0
1091
+
1092
+ bridge.teacher = _ScoreManyTeacherAdapter(_EmptyAlignmentTeacher())
1093
+ bridge._text_teacher_batcher = None
1094
+ bridge.score(0, 2, [10, 11, 65, 66, 99])
1095
+
1096
+ assert bridge.empty_alignments == 1
1097
+ # the empty sequence moved empty_alignments, and left the granularity mean untouched.
1098
+ assert bridge.align_group_n == 1
1099
+ assert bridge.align_group_sum == 1.0
1100
+
1101
+
1102
+ def test_bridge_granularity_survives_resume_without_reading_the_coverage_aliases():
1103
+ # granularity_sum / granularity_n are LEGACY ALIASES for coverage. resuming granularity from
1104
+ # them would silently restore coverage under the granularity name, so an old-format state must
1105
+ # restart the accumulator at zero rather than inherit the wrong quantity.
1106
+ resumed = _text_bridge(_BridgeTeacher())
1107
+ state = dict(_resume_accounting())
1108
+ state.update({"align_group_sum": 6.0, "align_group_n": 3})
1109
+ carried = _TeacherAlignmentBridge(
1110
+ prompts=list(resumed.prompts),
1111
+ tokenizer=_BridgeTokenizer(),
1112
+ teacher=_ScoreManyTeacherAdapter(_BridgeTeacher()),
1113
+ thinking_prefill="",
1114
+ eos_token_ids=frozenset({99}),
1115
+ stop_sequences=(),
1116
+ mutation_callback=lambda: None,
1117
+ initial_state=state,
1118
+ )
1119
+ assert carried.align_group_sum == 6.0
1120
+ assert carried.align_group_n == 3
1121
+ snapshot = carried.accounting_snapshot()
1122
+ assert snapshot["align_group_sum"] == 6.0
1123
+ assert snapshot["align_group_n"] == 3
1124
+
1125
+ legacy_only = _TeacherAlignmentBridge(
1126
+ prompts=list(resumed.prompts),
1127
+ tokenizer=_BridgeTokenizer(),
1128
+ teacher=_ScoreManyTeacherAdapter(_BridgeTeacher()),
1129
+ thinking_prefill="",
1130
+ eos_token_ids=frozenset({99}),
1131
+ stop_sequences=(),
1132
+ mutation_callback=lambda: None,
1133
+ initial_state=_resume_accounting(),
1134
+ )
1135
+ # granularity_sum is 3.5 and granularity_n is 5 in that state; both must be ignored here.
1136
+ assert legacy_only.align_group_sum == 0.0
1137
+ assert legacy_only.align_group_n == 0
1138
+ # the coverage accumulators, whose aliases those really are, DO still resume from them.
1139
+ assert legacy_only.coverage_sum == 3.5
1140
+ assert legacy_only.aligned_sequences == 5
1141
+
1142
+
1054
1143
  def test_text_teacher_batcher_enforces_max_batch_size_across_concurrent_requests():
1055
1144
  prompt_texts = [f"question-{index}" for index in range(17)]
1056
1145
  teacher = _BatchingTeacher(prompt_texts)
@@ -4919,6 +5008,109 @@ def test_deterministic_seed_uses_every_rollout_identity_component():
4919
5008
 
4920
5009
 
4921
5010
 
5011
+ def test_train_meta_records_the_optimizer_steps_that_actually_produced_a_loss():
5012
+ import inspect
5013
+
5014
+ from flash.engine.worker.opd_verl import run_opd_verl
5015
+
5016
+ notes = inspect.getsource(run_opd_verl)
5017
+ notes = notes[notes.index("_w.write_train_meta(") :]
5018
+ # `steps` is the REQUESTED horizon. a step whose batch carried no teacher signal never applies
5019
+ # an update, so reporting the horizon as the work done would overstate a partly-starved run.
5020
+ assert '"steps": update_horizon,' in notes
5021
+ assert '"opt_steps": len(final_accounting["loss_curve"]),' in notes
5022
+
5023
+
5024
+ def test_worker_refuses_to_publish_a_loss_curve_shorter_than_the_final_checkpoint():
5025
+ import inspect
5026
+
5027
+ from flash.engine.worker.opd_verl import run_opd_verl
5028
+
5029
+ source = inspect.getsource(run_opd_verl)
5030
+ # record_step only checks that each metric line FOLLOWS the previous one, so it cannot notice a
5031
+ # MISSING TRAILING metric: on_line silently skips any step-tagged line whose loss it cannot
5032
+ # parse, and no later step ever arrives to trip the sequence check. that leaves a curve of
5033
+ # length N-1 for a run verl actually checkpointed at step N, and opt_steps is published straight
5034
+ # from that curve. the guard must compare against final_step, not merely require non-emptiness.
5035
+ assert 'if len(final_accounting["loss_curve"]) != final_step:' in source
5036
+ guard = source[source.index('if len(final_accounting["loss_curve"]) != final_step:') :]
5037
+ assert "raise RuntimeError(" in guard[:600]
5038
+ # and it must sit BEFORE the publish, not after it.
5039
+ assert source.index('if len(final_accounting["loss_curve"]) != final_step:') < source.index(
5040
+ "_w.write_train_meta("
5041
+ )
5042
+
5043
+
5044
+ def test_train_meta_reports_the_teacher_call_shape_only_where_one_is_enforced():
5045
+ import inspect
5046
+
5047
+ from flash.engine.worker.opd_verl import run_opd_verl
5048
+
5049
+ notes = inspect.getsource(run_opd_verl)
5050
+ notes = notes[notes.index("_w.write_train_meta(") :]
5051
+ # only the single-turn text path runs through the batcher, and it holds one serial scoring
5052
+ # thread. multimodal scores one item per call and multi-turn batches an episode, both on the
5053
+ # bridge's own request threads -- neither has a constant to report.
5054
+ # the reported cap is bounded by the samples one step can actually produce, matching trl's
5055
+ # _opd_teacher_batch_size: a 1-rollout step can never fill a batch of 8, so publishing the
5056
+ # global cap there would describe a shape the run is structurally unable to reach.
5057
+ assert (
5058
+ '"opd_teacher_batch_size": (\n'
5059
+ " min(_TEXT_TEACHER_BATCH_SIZE, max(1, prompts_per_step * knobs.group_size))\n"
5060
+ " if not multimodal and not multi_turn\n"
5061
+ " else None\n"
5062
+ " ),"
5063
+ ) in notes
5064
+ assert (
5065
+ '"opd_teacher_workers": 1 if not multimodal and not multi_turn else None,'
5066
+ ) in notes
5067
+ # the engine length handed to vllm, not a hardcoded default: prompt filtering is carved out of
5068
+ # this same number, so a fabricated one would disagree with the budget the run actually used.
5069
+ assert '"vllm_max_model_len": max_model_len,' in notes
5070
+
5071
+
5072
+ def test_text_teacher_batcher_scores_one_batch_at_a_time():
5073
+ # opd_teacher_workers reports 1 for the text path. that is only honest while the batcher scores
5074
+ # serially, so assert the observable property rather than the thread count: no two score_many
5075
+ # calls may ever overlap, however the batcher is wired internally.
5076
+ from flash.engine.worker.opd_verl import _TextTeacherBatcher
5077
+
5078
+ class _OverlapDetectingTeacher:
5079
+ def __init__(self):
5080
+ self.lock = threading.Lock()
5081
+ self.in_flight = 0
5082
+ self.max_in_flight = 0
5083
+
5084
+ def score_many(self, items):
5085
+ with self.lock:
5086
+ self.in_flight += 1
5087
+ self.max_in_flight = max(self.max_in_flight, self.in_flight)
5088
+ # hold the call open long enough that a second scoring thread would land inside it.
5089
+ time.sleep(0.05)
5090
+ with self.lock:
5091
+ self.in_flight -= 1
5092
+ return [
5093
+ [TeacherToken(text=completion, logprob=-1.0, start=0, end=len(completion))]
5094
+ for _prompt, completion in items
5095
+ ]
5096
+
5097
+ teacher = _OverlapDetectingTeacher()
5098
+ batcher = _TextTeacherBatcher(teacher, max_batch_size=1, flush_wait_s=0.01)
5099
+ batcher.start()
5100
+ try:
5101
+ with ThreadPoolExecutor(max_workers=8) as executor:
5102
+ futures = [
5103
+ executor.submit(batcher.score, f"prompt-{index}", f"completion-{index}")
5104
+ for index in range(8)
5105
+ ]
5106
+ for future in futures:
5107
+ future.result(timeout=10.0)
5108
+ finally:
5109
+ batcher.close()
5110
+
5111
+ assert teacher.max_in_flight == 1
5112
+
5113
+
4922
5114
  def test_worker_structured_validator_runs_before_model_download():
4923
5115
  import inspect
4924
5116
 
@@ -1025,7 +1025,7 @@ wheels = [
1025
1025
 
1026
1026
  [[package]]
1027
1027
  name = "freesolo-flash"
1028
- version = "1.0.43"
1028
+ version = "1.0.45"
1029
1029
  source = { editable = "." }
1030
1030
 
1031
1031
  [package.optional-dependencies]