freesolo-flash-dev 1.0.4__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 (360) hide show
  1. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/PKG-INFO +1 -1
  2. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/cli/training_doc.py +11 -8
  3. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/client/http.py +16 -7
  4. freesolo_flash_dev-1.0.5/flash/engine/multiturn_reward_scoring.py +73 -0
  5. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/multiturn_rollout.py +43 -35
  6. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/adapter.py +68 -15
  7. freesolo_flash_dev-1.0.5/flash/engine/worker/grpo_perturn_trainer.py +138 -0
  8. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/heartbeat.py +66 -1
  9. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/lora.py +14 -0
  10. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/opd_vllm.py +77 -14
  11. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/rl.py +110 -4
  12. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/sft.py +713 -134
  13. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/envs/adapter.py +74 -8
  14. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/envs/base.py +40 -0
  15. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/_poll.py +5 -0
  16. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/schema/__init__.py +2 -0
  17. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/schema/fields.py +23 -1
  18. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/serve/deploy.py +141 -34
  19. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/server/auth.py +44 -19
  20. freesolo_flash_dev-1.0.5/flash/server/db.py +248 -0
  21. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/spec.py +32 -1
  22. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/pyproject.toml +2 -2
  23. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/conftest.py +2 -0
  24. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_cli_commands.py +11 -0
  25. freesolo_flash_dev-1.0.5/tests/test_client_stream_reads.py +109 -0
  26. freesolo_flash_dev-1.0.5/tests/test_deploy_latency.py +418 -0
  27. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_flash_worker.py +20 -36
  28. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_grpo_params.py +1 -0
  29. freesolo_flash_dev-1.0.5/tests/test_grpo_perturn.py +431 -0
  30. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_multimodal_training.py +17 -0
  31. freesolo_flash_dev-1.0.5/tests/test_multiturn_per_turn_reward.py +379 -0
  32. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_multiturn_rollout.py +7 -3
  33. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_multiturn_rollout_coverage.py +6 -2
  34. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_multiturn_rollout_request_policy.py +2 -0
  35. freesolo_flash_dev-1.0.5/tests/test_named_reward_metrics.py +79 -0
  36. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_opd.py +11 -0
  37. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_opd_vllm.py +142 -0
  38. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_serve.py +44 -9
  39. freesolo_flash_dev-1.0.5/tests/test_server_auth_singleflight.py +82 -0
  40. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_server_db.py +238 -0
  41. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_serving_contract.py +21 -4
  42. freesolo_flash_dev-1.0.5/tests/test_sft_activation_shaping.py +140 -0
  43. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_sft_example_selection.py +15 -0
  44. freesolo_flash_dev-1.0.5/tests/test_sft_tokenize_cache.py +375 -0
  45. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_spec_and_validation.py +42 -2
  46. freesolo_flash_dev-1.0.5/tests/test_warmstart_adapter_download.py +282 -0
  47. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_warmstart_cross_repo.py +15 -1
  48. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_worker_init_heartbeat.py +49 -0
  49. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/uv.lock +1 -1
  50. freesolo_flash_dev-1.0.4/flash/server/db.py +0 -134
  51. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/.claude/skills/verify/SKILL.md +0 -0
  52. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/.dockerignore +0 -0
  53. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/.env.example +0 -0
  54. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/.github/workflows/auto-rebake.yml +0 -0
  55. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/.github/workflows/bake-kernel-cache.yml +0 -0
  56. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/.github/workflows/ci.yml +0 -0
  57. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/.github/workflows/main-source-guard.yml +0 -0
  58. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/.github/workflows/notify-tests-repo.yml +0 -0
  59. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/.github/workflows/publish-dev.yml +0 -0
  60. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/.github/workflows/publish-image.yml +0 -0
  61. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/.github/workflows/publish.yml +0 -0
  62. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/.github/workflows/version-parity.yml +0 -0
  63. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/.github/workflows/worker-image.yml +0 -0
  64. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/.gitignore +0 -0
  65. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/Dockerfile +0 -0
  66. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/Dockerfile.worker +0 -0
  67. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/LICENSE +0 -0
  68. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/README.md +0 -0
  69. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/build/kernel_cache/.gitignore +0 -0
  70. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/build/kernel_cache/.keep +0 -0
  71. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/docker/Dockerfile.kernelcache +0 -0
  72. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/docker/Dockerfile.kernelcache.relayer +0 -0
  73. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/docker/bake_kernel_cache.py +0 -0
  74. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/docker/bake_pod_entry.py +0 -0
  75. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/docker/kernel_fingerprint.py +0 -0
  76. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/docker/make_rp_handler.py +0 -0
  77. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/__init__.py +0 -0
  78. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/_channel.py +0 -0
  79. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/_fileio.py +0 -0
  80. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/_logging.py +0 -0
  81. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/_update_check.py +0 -0
  82. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/adapter_artifacts.py +0 -0
  83. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/catalog.py +0 -0
  84. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/cli/__init__.py +0 -0
  85. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/cli/__main__.py +0 -0
  86. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/cli/_tty.py +0 -0
  87. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/cli/commands.py +0 -0
  88. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/cli/env_setup.py +0 -0
  89. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/cli/envpush.py +0 -0
  90. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/cli/render.py +0 -0
  91. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/client/__init__.py +0 -0
  92. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/client/config.py +0 -0
  93. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/client/runtime_secrets.py +0 -0
  94. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/client/specs.py +0 -0
  95. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/cost/__init__.py +0 -0
  96. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/cost/analytical.py +0 -0
  97. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/cost/facts.py +0 -0
  98. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/cost/spec.py +0 -0
  99. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/cost/types.py +0 -0
  100. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/diagnostics.py +0 -0
  101. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/__init__.py +0 -0
  102. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/accounting.py +0 -0
  103. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/chalk_kernels.py +0 -0
  104. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/recipe.py +0 -0
  105. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/steps.py +0 -0
  106. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/structured_outputs.py +0 -0
  107. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/vram.py +0 -0
  108. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/__init__.py +0 -0
  109. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/__main__.py +0 -0
  110. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/_pkg.py +0 -0
  111. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/decoding.py +0 -0
  112. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/finalize.py +0 -0
  113. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/gpu_setup.py +0 -0
  114. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/grpo.py +0 -0
  115. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/grpo_multimodal.py +0 -0
  116. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/hf.py +0 -0
  117. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/kernel_warmup.py +0 -0
  118. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/opd.py +1 -1
  119. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/opd_gkd.py +0 -0
  120. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/packing.py +0 -0
  121. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/perf/__init__.py +0 -0
  122. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/perf/attn.py +0 -0
  123. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/perf/diagnostics.py +0 -0
  124. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/perf/lifecycle.py +0 -0
  125. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/perf/liger.py +0 -0
  126. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/perf/loraplus.py +0 -0
  127. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/perf/memory.py +0 -0
  128. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/rng.py +0 -0
  129. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/teacher.py +0 -0
  130. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/tokenizer_align.py +0 -0
  131. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker/wandb_log.py +0 -0
  132. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/engine/worker_entrypoint.py +0 -0
  133. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/envs/__init__.py +0 -0
  134. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/envs/archive.py +0 -0
  135. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/envs/archive_policy.py +0 -0
  136. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/envs/loader.py +0 -0
  137. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/envs/pull.py +0 -0
  138. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/envs/registry.py +0 -0
  139. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/lora_rank.py +0 -0
  140. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/multimodal.py +0 -0
  141. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/opd_retry_contract.py +0 -0
  142. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/__init__.py +0 -0
  143. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/_auth.py +0 -0
  144. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/_deadline.py +0 -0
  145. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/_hf_artifacts.py +0 -0
  146. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/_hf_retry.py +0 -0
  147. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/_http.py +0 -0
  148. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/_instance.py +0 -0
  149. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/_instance_bootstrap.py +0 -0
  150. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/_instance_poll.py +0 -0
  151. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/_instance_provider.py +0 -0
  152. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/_worker.py +0 -0
  153. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/allocator.py +0 -0
  154. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/base.py +0 -0
  155. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/lambdalabs/__init__.py +0 -0
  156. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/lambdalabs/api.py +0 -0
  157. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/lambdalabs/auth.py +0 -0
  158. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/lambdalabs/gpus.py +0 -0
  159. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/lambdalabs/jobs/__init__.py +0 -0
  160. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/lambdalabs/jobs/builders.py +0 -0
  161. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/lambdalabs/preflight.py +0 -0
  162. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/lambdalabs/pricing.py +0 -0
  163. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/preflight.py +0 -0
  164. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/realized.py +0 -0
  165. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/runpod/__init__.py +0 -0
  166. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/runpod/api.py +0 -0
  167. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/runpod/auth.py +0 -0
  168. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/runpod/cost.py +0 -0
  169. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/runpod/gpus.py +0 -0
  170. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/runpod/jobs.py +0 -0
  171. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/runpod/keys.py +0 -0
  172. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/runpod/preflight.py +0 -0
  173. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/runpod/preload.py +0 -0
  174. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/runpod/pricing.py +0 -0
  175. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/runpod/slots.py +0 -0
  176. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/runpod/train/__init__.py +0 -0
  177. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/runpod/train/endpoints.py +0 -0
  178. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/vast/__init__.py +0 -0
  179. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/vast/api.py +0 -0
  180. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/vast/auth.py +0 -0
  181. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/vast/jobs/__init__.py +0 -0
  182. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/vast/jobs/builders.py +0 -0
  183. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/vast/preflight.py +0 -0
  184. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/providers/vast/pricing.py +0 -0
  185. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/py.typed +0 -0
  186. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/runner/__init__.py +0 -0
  187. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/runner/checkpoints.py +0 -0
  188. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/runner/deploy.py +0 -0
  189. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/runner/lifecycle.py +0 -0
  190. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/runner/verified_revisions.py +0 -0
  191. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/serve/__init__.py +0 -0
  192. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/serve/export.py +0 -0
  193. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/serve/pricing.py +0 -0
  194. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/serve/urls.py +0 -0
  195. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/server/__init__.py +0 -0
  196. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/server/__main__.py +0 -0
  197. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/server/_deps.py +0 -0
  198. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/server/_internal_client.py +0 -0
  199. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/server/_locks.py +0 -0
  200. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/server/_runtime.py +0 -0
  201. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/server/app.py +0 -0
  202. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/server/billing.py +0 -0
  203. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/server/billing_retry.py +0 -0
  204. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/server/checkpoints.py +0 -0
  205. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/server/environment_registry.py +0 -0
  206. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/server/envs.py +0 -0
  207. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/server/reconcile.py +0 -0
  208. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/server/repo_cleanup.py +0 -0
  209. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/server/routes/__init__.py +0 -0
  210. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/server/routes/envs.py +0 -0
  211. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/server/routes/meta.py +0 -0
  212. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/server/routes/runs.py +0 -0
  213. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/server/routes/serving.py +0 -0
  214. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/flash/server/run_registry.py +0 -0
  215. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/infisical-entrypoint.sh +0 -0
  216. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/scripts/build_dev_dist.py +0 -0
  217. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/__init__.py +0 -0
  218. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/_helpers/__init__.py +0 -0
  219. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/_helpers/runner.py +0 -0
  220. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/_helpers/specs.py +0 -0
  221. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/_helpers/vast.py +0 -0
  222. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/fixtures/math_eval.jsonl +0 -0
  223. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/fixtures/math_train.jsonl +0 -0
  224. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/live/__init__.py +0 -0
  225. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/live/conftest.py +0 -0
  226. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/live/test_lambda_live.py +0 -0
  227. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/live/test_runpod_live.py +0 -0
  228. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/live/test_vast_live.py +0 -0
  229. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_accounting_coverage.py +0 -0
  230. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_agent_flash_cli_contract.py +0 -0
  231. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_algorithms.py +0 -0
  232. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_allocator.py +0 -0
  233. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_async_uploads.py +0 -0
  234. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_b200_rollout_opt.py +0 -0
  235. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_backend_jobspec_contract.py +0 -0
  236. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_base_model_provenance.py +0 -0
  237. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_billing_retry.py +0 -0
  238. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_cancel_remote.py +0 -0
  239. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_catalog_consistency.py +0 -0
  240. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_chalk_kernels.py +0 -0
  241. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_charge_pricing.py +0 -0
  242. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_checkpoints.py +0 -0
  243. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_cli_commands_coverage.py +0 -0
  244. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_cli_errors.py +0 -0
  245. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_cli_estimate.py +0 -0
  246. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_cli_help.py +0 -0
  247. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_cli_main_coverage.py +0 -0
  248. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_cli_managed.py +0 -0
  249. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_cli_render_coverage.py +0 -0
  250. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_cli_render_theme.py +0 -0
  251. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_client.py +0 -0
  252. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_client_server_integration.py +0 -0
  253. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_config_overrides.py +0 -0
  254. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_controlled_experiment_repairs.py +0 -0
  255. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_cost_analytical.py +0 -0
  256. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_cost_equation.py +0 -0
  257. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_cost_estimate.py +0 -0
  258. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_cost_hardware.py +0 -0
  259. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_cost_models.py +0 -0
  260. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_cost_rewards.py +0 -0
  261. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_dev_channel.py +0 -0
  262. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_disk_gb.py +0 -0
  263. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_endpoint_name.py +0 -0
  264. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_env_cache_evict.py +0 -0
  265. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_env_delete.py +0 -0
  266. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_env_download.py +0 -0
  267. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_env_download_api.py +0 -0
  268. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_env_no_install.py +0 -0
  269. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_env_publish.py +0 -0
  270. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_env_pull.py +0 -0
  271. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_env_pull_coverage.py +0 -0
  272. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_env_pull_loader_coverage.py +0 -0
  273. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_env_pull_managed_control_plane.py +0 -0
  274. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_env_push.py +0 -0
  275. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_env_rate_limit_resolve.py +0 -0
  276. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_envs_coverage.py +0 -0
  277. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_export.py +0 -0
  278. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_flash_mvp.py +0 -0
  279. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_flashinfer_cache_dirs.py +0 -0
  280. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_github_urlopen_retry.py +0 -0
  281. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_gpus.py +0 -0
  282. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_grpo_mask_aware.py +0 -0
  283. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_grpo_sleep_gate.py +0 -0
  284. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_health_liveness.py +0 -0
  285. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_hf_retry.py +0 -0
  286. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_idle_endpoint_reaper.py +0 -0
  287. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_instance_bootstrap_coverage.py +0 -0
  288. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_internal_client.py +0 -0
  289. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_jobs.py +0 -0
  290. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_kernel_cache.py +0 -0
  291. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_kernel_fingerprint.py +0 -0
  292. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_kv_util.py +0 -0
  293. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_lambda_api_coverage.py +0 -0
  294. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_lambda_runner.py +0 -0
  295. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_logging.py +0 -0
  296. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_login_perms.py +0 -0
  297. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_lora_rank_coverage.py +0 -0
  298. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_lora_rank_preflight.py +0 -0
  299. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_managed_hf_repo.py +0 -0
  300. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_metrics_schema_agent_contract.py +0 -0
  301. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_mig_guard.py +0 -0
  302. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_multimodal_input_grads.py +0 -0
  303. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_multiturn_image_rollout.py +0 -0
  304. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_multiturn_rollout_records.py +0 -0
  305. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_oom_escalate_gpu.py +0 -0
  306. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_opd_full_state_resume.py +0 -0
  307. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_opd_resume_safety.py +0 -0
  308. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_open_model_policy.py +0 -0
  309. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_orchestrator_flash.py +0 -0
  310. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_packing.py +0 -0
  311. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_packing_coverage.py +0 -0
  312. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_poll_helpers.py +0 -0
  313. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_preflight.py +0 -0
  314. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_provider_preflight_coverage.py +0 -0
  315. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_provider_routing.py +0 -0
  316. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_provider_teardown_robustness.py +0 -0
  317. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_providers_symmetry.py +0 -0
  318. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_realized_cost.py +0 -0
  319. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_repo_cleanup.py +0 -0
  320. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_resolve_params_b.py +0 -0
  321. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_resume_on_retry.py +0 -0
  322. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_runmgmt.py +0 -0
  323. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_runpod_api_delete.py +0 -0
  324. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_runpod_key_fingerprint.py +0 -0
  325. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_runpod_key_waterfall.py +0 -0
  326. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_runpod_slots.py +0 -0
  327. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_server_api.py +0 -0
  328. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_server_app_coverage.py +0 -0
  329. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_server_billing.py +0 -0
  330. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_server_checkpoints_coverage.py +0 -0
  331. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_server_envs_coverage.py +0 -0
  332. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_server_locks_coverage.py +0 -0
  333. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_serving_context_preflight.py +0 -0
  334. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_serving_pricing_coverage.py +0 -0
  335. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_serving_schema_coverage.py +0 -0
  336. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_sft_gc_off.py +0 -0
  337. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_sft_max_context.py +0 -0
  338. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_single_turn_multimodal_grpo.py +0 -0
  339. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_structured_outputs.py +0 -0
  340. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_thinking_config.py +0 -0
  341. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_train_context_preflight.py +0 -0
  342. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_training_controls.py +0 -0
  343. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_update_check.py +0 -0
  344. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_vast_api.py +0 -0
  345. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_vast_api_coverage.py +0 -0
  346. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_vast_offers.py +0 -0
  347. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_vast_provider_coverage.py +0 -0
  348. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_vast_runner.py +0 -0
  349. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_verified_revisions.py +0 -0
  350. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_verifiers.py +0 -0
  351. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_version.py +0 -0
  352. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_vl_warmstart_adapter_keys.py +0 -0
  353. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_wandb_log_coverage.py +0 -0
  354. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_wandb_naming.py +0 -0
  355. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_weight_cache.py +0 -0
  356. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_worker_dryrun.py +0 -0
  357. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_worker_hardexit.py +0 -0
  358. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_worker_image.py +0 -0
  359. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/tests/test_worker_stack.py +0 -0
  360. {freesolo_flash_dev-1.0.4 → freesolo_flash_dev-1.0.5}/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.4
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
@@ -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
 
@@ -512,13 +512,22 @@ class ApiClient:
512
512
  if content:
513
513
  yield str(content)
514
514
  return
515
- while raw := resp.read(1):
516
- chunk = decoder.decode(raw)
517
- if chunk:
518
- yield chunk
519
- tail = decoder.decode(b"", final=True)
520
- if tail:
521
- yield tail
515
+ read1 = getattr(resp, "read1", None)
516
+ read = read1 if read1 is not None else resp.read
517
+ read_size = 4096 if read1 is not None else 1
518
+ while raw := read(read_size):
519
+ state = decoder.getstate()
520
+ try:
521
+ decoded = decoder.decode(raw)
522
+ except UnicodeDecodeError as exc:
523
+ decoder.setstate(state)
524
+ prefix_end = max(0, exc.start - len(state[0]))
525
+ yield from decoder.decode(raw[:prefix_end])
526
+ # bind + re-raise explicitly: the yield above clears the active exception, so a
527
+ # bare `raise` here would fail with "No active exception to reraise".
528
+ raise exc
529
+ yield from decoded
530
+ yield from decoder.decode(b"", final=True)
522
531
 
523
532
 
524
533
  def client_from_config(require_key: bool = True) -> ApiClient:
@@ -0,0 +1,73 @@
1
+ """Typed terminal reward scoring for multi-turn GRPO rollouts."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+ from dataclasses import dataclass
7
+
8
+ from flash.envs.base import BaseEnvironment, RolloutReward
9
+
10
+
11
+ @dataclass(frozen=True)
12
+ class RolloutScoreRequest:
13
+ example: dict
14
+ state: dict
15
+ turn_count: int
16
+
17
+
18
+ def _validated_reward(reward: RolloutReward, request: RolloutScoreRequest) -> RolloutReward:
19
+ episode = float(reward.episode)
20
+ # a non-finite episode reward is unscorable and has no valid scalar fallback. canonicalize it to
21
+ # nan, which is trl's ONLY unscorable marker (trl uses torch.isnan to exclude the row from the
22
+ # group baseline and then nan_to_num zeros its advantage, matching stock grpo). forwarding a raw
23
+ # inf instead would NOT be recognized as unscorable and would contaminate the whole group with
24
+ # huge advantages. per-turn credit is disabled so the unscorable row is never revived.
25
+ if not math.isfinite(episode):
26
+ print(
27
+ "[grpo][warn] episode reward non-finite; rollout unscorable, per-turn credit disabled"
28
+ )
29
+ return RolloutReward(episode=float("nan"), turns=None)
30
+ if reward.turns is None:
31
+ return RolloutReward(episode=episode, turns=None)
32
+ if not isinstance(reward.turns, (list, tuple)):
33
+ # only an ordered list/tuple carries a well-defined per-turn order. a str, bytes,
34
+ # bytearray, mapping, or unordered set would still iterate into floats and could pass
35
+ # the count check while assigning rewards to the wrong turns -- reject and fall back.
36
+ print(
37
+ "[grpo][warn] per-turn rewards unavailable (turns is not an ordered list/tuple); "
38
+ "using episode reward"
39
+ )
40
+ return RolloutReward(episode=episode, turns=None)
41
+
42
+ reason: str | None = None
43
+ coerced: tuple[float, ...] | None = None
44
+ try:
45
+ coerced = tuple(float(value) for value in reward.turns)
46
+ except (TypeError, ValueError):
47
+ reason = "per-turn rewards contain a non-number"
48
+ else:
49
+ if len(coerced) != request.turn_count:
50
+ reason = f"received {len(coerced)} reward(s) for {request.turn_count} assistant turn(s)"
51
+ elif not all(math.isfinite(value) for value in coerced):
52
+ reason = "per-turn rewards contain a non-finite value"
53
+
54
+ if reason is not None:
55
+ print(f"[grpo][warn] per-turn rewards unavailable ({reason}); using episode reward")
56
+ return RolloutReward(episode=episode, turns=None)
57
+ return RolloutReward(episode=episode, turns=coerced)
58
+
59
+
60
+ def score_rollouts(active_env, requests: list[RolloutScoreRequest]) -> list[RolloutReward]:
61
+ """Score terminal rollout states once and return normalized typed rewards."""
62
+ items = [(request.example, request.state) for request in requests]
63
+ rollout_rewards_many = getattr(active_env, "rollout_rewards_many", None)
64
+ if callable(rollout_rewards_many):
65
+ rewards = rollout_rewards_many(items)
66
+ else:
67
+ rewards = BaseEnvironment.rollout_rewards_many(active_env, items)
68
+ if len(rewards) != len(requests):
69
+ raise RuntimeError("env.rollout_rewards_many returned the wrong number of rewards")
70
+ return [
71
+ _validated_reward(reward, request)
72
+ for reward, request in zip(rewards, requests, strict=True)
73
+ ]
@@ -16,10 +16,11 @@ import threading
16
16
  import time
17
17
  from collections import OrderedDict
18
18
  from collections.abc import Callable
19
- from concurrent.futures import ThreadPoolExecutor, as_completed
20
19
  from dataclasses import dataclass
21
20
  from typing import TypedDict
22
21
 
22
+ from flash.engine.multiturn_reward_scoring import RolloutScoreRequest, score_rollouts
23
+
23
24
 
24
25
  class RolloutResult(TypedDict):
25
26
  """Token-aligned fields returned per rollout."""
@@ -29,6 +30,8 @@ class RolloutResult(TypedDict):
29
30
  logprobs: list[float]
30
31
  env_mask: list[int]
31
32
  reward: float
33
+ turn_spans: list[tuple[int, int]]
34
+ turn_rewards: list[float] | None
32
35
 
33
36
 
34
37
  RolloutCompletion = tuple[str, list[int], list[float], str]
@@ -71,6 +74,8 @@ _ROLLOUT_FIELDS: tuple[str, ...] = (
71
74
  "logprobs",
72
75
  "env_mask",
73
76
  "reward",
77
+ "turn_spans",
78
+ "turn_rewards",
74
79
  )
75
80
 
76
81
 
@@ -174,6 +179,7 @@ def rollout_one(
174
179
  completion_ids: list[int] = []
175
180
  logprobs: list[float] = []
176
181
  env_mask: list[int] = []
182
+ turn_spans: list[tuple[int, int]] = []
177
183
 
178
184
  turns = 0
179
185
  while True:
@@ -184,7 +190,9 @@ def rollout_one(
184
190
  break
185
191
  max_new = min(max_new, remaining)
186
192
  asst_ids, asst_lp, text = generate(cur_ids, max_new)
193
+ turn_start = len(completion_ids)
187
194
  completion_ids.extend(asst_ids)
195
+ turn_spans.append((turn_start, len(completion_ids)))
188
196
  logprobs.extend(asst_lp)
189
197
  env_mask.extend([1] * len(asst_ids))
190
198
  cur_ids.extend(asst_ids)
@@ -212,13 +220,18 @@ def rollout_one(
212
220
  env_mask.extend([0] * len(glue))
213
221
  cur_ids.extend(glue)
214
222
 
215
- reward = active_env.reward("", example, state)
223
+ score = score_rollouts(
224
+ active_env,
225
+ [RolloutScoreRequest(example=example, state=state, turn_count=len(turn_spans))],
226
+ )[0]
216
227
  return {
217
228
  "prompt_ids": prompt_ids,
218
229
  "completion_ids": completion_ids,
219
230
  "logprobs": logprobs,
220
231
  "env_mask": env_mask,
221
- "reward": float(reward),
232
+ "reward": score.episode,
233
+ "turn_spans": turn_spans,
234
+ "turn_rewards": list(score.turns) if score.turns is not None else None,
222
235
  }
223
236
 
224
237
 
@@ -331,6 +344,7 @@ class _RolloutState:
331
344
  "messages",
332
345
  "prompt_ids",
333
346
  "state",
347
+ "turn_spans",
334
348
  "turns",
335
349
  )
336
350
 
@@ -343,18 +357,21 @@ class _RolloutState:
343
357
  self.completion_ids: list[int] = []
344
358
  self.logprobs: list[float] = []
345
359
  self.env_mask: list[int] = []
360
+ self.turn_spans: list[tuple[int, int]] = []
346
361
  self.state = state
347
362
  self.turns = 0
348
363
  self.budget = budget
349
364
  self.done = False
350
365
 
351
- def result(self, reward: float) -> RolloutResult:
366
+ def result(self, reward: float, turn_rewards: list[float] | None) -> RolloutResult:
352
367
  return {
353
368
  "prompt_ids": self.prompt_ids,
354
369
  "completion_ids": self.completion_ids,
355
370
  "logprobs": self.logprobs,
356
371
  "env_mask": self.env_mask,
357
372
  "reward": float(reward),
373
+ "turn_spans": self.turn_spans,
374
+ "turn_rewards": turn_rewards,
358
375
  }
359
376
 
360
377
 
@@ -369,7 +386,9 @@ def _advance_after_turn(
369
386
  max_turns: int,
370
387
  ) -> None:
371
388
  """Fold one assistant turn into ``r`` and run its env step. Sets ``r.done`` when finished."""
389
+ turn_start = len(r.completion_ids)
372
390
  r.completion_ids.extend(asst_ids)
391
+ r.turn_spans.append((turn_start, len(r.completion_ids)))
373
392
  r.logprobs.extend(asst_lp)
374
393
  r.env_mask.extend([1] * len(asst_ids))
375
394
  r.cur_ids.extend(asst_ids)
@@ -465,31 +484,6 @@ def _turn_budget(r: _RolloutState, per_turn_max_tokens: int) -> int | None:
465
484
  return max(1, max_new)
466
485
 
467
486
 
468
- def _score_rollouts(active_env, rollouts: list[_RolloutState]) -> list[float]:
469
- """Reward each rollout in input order, using reward_many, concurrent, or serial scoring."""
470
- reward_many = getattr(active_env, "reward_many", None)
471
- if callable(reward_many):
472
- rewards = reward_many([(r.example, r.state) for r in rollouts])
473
- if len(rewards) != len(rollouts):
474
- raise RuntimeError("env.reward_many returned the wrong number of rewards")
475
- return [float(x) for x in rewards]
476
-
477
- def _score(r: _RolloutState) -> float:
478
- return float(active_env.reward("", r.example, r.state))
479
-
480
- if len(rollouts) <= 1 or not getattr(active_env, "reward_thread_safe", True):
481
- return [_score(r) for r in rollouts]
482
- pool = ThreadPoolExecutor(max_workers=min(16, len(rollouts)))
483
- try:
484
- futures = {pool.submit(_score, r): i for i, r in enumerate(rollouts)}
485
- scores: list[float] = [0.0] * len(rollouts)
486
- for fut in as_completed(futures):
487
- scores[futures[fut]] = fut.result() # re-raises the first failed scorer
488
- finally:
489
- pool.shutdown(wait=True, cancel_futures=True)
490
- return scores
491
-
492
-
493
487
  _PHYSICAL_REQUEST_COUNTER = itertools.count()
494
488
  _PHYSICAL_REQUEST_COUNTER_LOCK = threading.Lock()
495
489
  _ROLLOUT_REQUEST_TIMEOUT_MIN_SECONDS = 600.0
@@ -690,8 +684,22 @@ def rollout_async(
690
684
  to_env.put(None)
691
685
  worker.join()
692
686
 
693
- rewards = _score_rollouts(active_env, rollouts)
694
- return [r.result(rw) for r, rw in zip(rollouts, rewards, strict=True)]
687
+ requests = [
688
+ RolloutScoreRequest(
689
+ example=rollout.example,
690
+ state=rollout.state,
691
+ turn_count=len(rollout.turn_spans),
692
+ )
693
+ for rollout in rollouts
694
+ ]
695
+ scores = score_rollouts(active_env, requests)
696
+ return [
697
+ rollout.result(
698
+ score.episode,
699
+ list(score.turns) if score.turns is not None else None,
700
+ )
701
+ for rollout, score in zip(rollouts, scores, strict=True)
702
+ ]
695
703
 
696
704
 
697
705
  def render_message_ids(tok, messages, add_generation_prompt: bool, *, thinking: bool) -> list[int]:
@@ -964,10 +972,10 @@ def build_rollout_func(
964
972
  monotonic=monotonic,
965
973
  request_id_factory=request_id_factory,
966
974
  )
967
- out: dict[str, list] = {k: [] for k in _ROLLOUT_FIELDS}
968
- for r in rollouts:
969
- for k in out:
970
- out[k].append(r[k])
975
+ out: dict[str, list] = {key: [] for key in _ROLLOUT_FIELDS}
976
+ for rollout in rollouts:
977
+ for key in out:
978
+ out[key].append(rollout[key])
971
979
  return out
972
980
  finally:
973
981
  # Abort in-flight requests on error so they don't corrupt the next GRPO step.
@@ -2,11 +2,21 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import json
5
6
  import os
7
+ import shutil
6
8
 
7
9
  from flash.engine.recipe import RECIPE
8
10
  from flash.engine.worker._pkg import W as _w
11
+ from flash.engine.worker.hf import (
12
+ RetriableInfraError,
13
+ _has_deployable_adapter,
14
+ _prefetch_error_is_retriable,
15
+ _require_hf_deadline_allowance,
16
+ _sleep_with_hf_deadline,
17
+ )
9
18
  from flash.engine.worker.lora import (
19
+ _read_adapter_tensor_keys,
10
20
  adapter_is_vl_warmstart,
11
21
  assert_adapter_delta_nonzero,
12
22
  assert_adapter_load_clean,
@@ -15,6 +25,9 @@ from flash.engine.worker.lora import (
15
25
  )
16
26
  from flash.engine.worker.perf import optimal_attn_impl
17
27
 
28
+ _ADAPTER_DOWNLOAD_RETRIES = 4
29
+ _ADAPTER_DOWNLOAD_BACKOFF_S = 5.0
30
+
18
31
 
19
32
  def make_lora(model_id: str | None = None):
20
33
  """Build LoRA config targeting all linear layers (VL models included: the vision tower /
@@ -196,6 +209,20 @@ def _resolve_adapter_ref(adapter_ref: str) -> tuple[str, str] | None:
196
209
  return parse_adapter_storage_ref(adapter_ref)
197
210
 
198
211
 
212
+ def _warmstart_adapter_is_loadable(adir: str) -> bool:
213
+ """return true only for a structurally complete adapter config and weight file."""
214
+ if not _has_deployable_adapter(adir):
215
+ return False
216
+ try:
217
+ with open(os.path.join(adir, "adapter_config.json"), encoding="utf-8") as config_file:
218
+ config = json.load(config_file)
219
+ if not isinstance(config, dict) or str(config.get("peft_type", "")).upper() != "LORA":
220
+ return False
221
+ return bool(_read_adapter_tensor_keys(adir))
222
+ except Exception:
223
+ return False
224
+
225
+
199
226
  def _download_adapter(adapter_prefix: str | None) -> str | None:
200
227
  """Download an init_from_adapter LoRA to /tmp/evdl/<prefix>/adapter and return its dir.
201
228
 
@@ -210,19 +237,45 @@ def _download_adapter(adapter_prefix: str | None) -> str | None:
210
237
  repo, prefix = resolved
211
238
  from huggingface_hub import snapshot_download
212
239
 
213
- try:
214
- snapshot_download(
215
- repo_id=repo,
216
- repo_type="dataset",
217
- allow_patterns=[f"{prefix}/adapter/*"],
218
- local_dir="/tmp/evdl",
219
- token=os.environ.get("HF_TOKEN"),
220
- revision=(_w.JOB_SPEC.train.init_from_adapter_revision if _w.JOB_SPEC else None)
221
- or None,
222
- )
223
- except Exception:
224
- raise RuntimeError(
225
- "the prepared warm-start source adapter could not be downloaded"
226
- ) from None
227
240
  adir = os.path.join("/tmp/evdl", prefix, "adapter")
228
- return adir if os.path.isdir(adir) else None
241
+ # start from a clean path so the loadable-check can only ever accept files THIS download
242
+ # materialized -- leftover materialization from an earlier worker subprocess, attempt, or a
243
+ # different run sharing the same prefix must not satisfy the post-exception loadable check and
244
+ # mask a terminal 404/403/429 for the current repo/revision.
245
+ shutil.rmtree(adir, ignore_errors=True)
246
+ for attempt in range(_ADAPTER_DOWNLOAD_RETRIES):
247
+ _require_hf_deadline_allowance()
248
+ try:
249
+ snapshot_download(
250
+ repo_id=repo,
251
+ repo_type="dataset",
252
+ allow_patterns=[f"{prefix}/adapter/*"],
253
+ local_dir="/tmp/evdl",
254
+ token=os.environ.get("HF_TOKEN"),
255
+ revision=(_w.JOB_SPEC.train.init_from_adapter_revision if _w.JOB_SPEC else None)
256
+ or None,
257
+ )
258
+ except Exception as error:
259
+ # a later nonessential sidecar may fail after the config and weights are already complete.
260
+ if _warmstart_adapter_is_loadable(adir):
261
+ return adir
262
+ if not _prefetch_error_is_retriable(error):
263
+ raise RuntimeError(
264
+ "the prepared warm-start source adapter could not be downloaded"
265
+ ) from None
266
+ else:
267
+ # a returned snapshot can still be incomplete: an interrupted transfer, or hf falling
268
+ # back to a partial local_dir when a throttled metadata call cannot confirm the file set.
269
+ if _warmstart_adapter_is_loadable(adir):
270
+ return adir
271
+ # discard partial local_dir materialization so the next attempt cannot reuse stale files.
272
+ shutil.rmtree(adir, ignore_errors=True)
273
+ if attempt + 1 < _ADAPTER_DOWNLOAD_RETRIES:
274
+ try:
275
+ if not _sleep_with_hf_deadline(_ADAPTER_DOWNLOAD_BACKOFF_S * (attempt + 1)):
276
+ break
277
+ except Exception:
278
+ break
279
+ raise RetriableInfraError(
280
+ "the prepared warm-start source adapter could not be downloaded after transient failures"
281
+ ) from None
@@ -0,0 +1,138 @@
1
+ """GRPO trainer support for group-relative per-turn credit assignment."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import cast
7
+
8
+ import torch
9
+ from trl import GRPOTrainer
10
+
11
+
12
+ @dataclass(frozen=True)
13
+ class TurnCreditRow:
14
+ spans: tuple[tuple[int, int], ...]
15
+ turns: tuple[float, ...] | None
16
+
17
+
18
+ def build_per_turn_advantages(
19
+ turn_spans_per_completion: list[list[tuple[int, int]]],
20
+ turn_rewards_per_completion: list[list[float] | None],
21
+ num_generations: int,
22
+ completion_len: int,
23
+ *,
24
+ episode_advantages: torch.Tensor,
25
+ ) -> torch.Tensor:
26
+ """Build token-aligned advantages from per-turn rewards in consecutive GRPO groups."""
27
+ batch_size = len(turn_spans_per_completion)
28
+ if len(turn_rewards_per_completion) != batch_size:
29
+ raise ValueError("turn span and reward row counts must match")
30
+ if num_generations <= 0 or batch_size % num_generations != 0:
31
+ raise ValueError("batch size must be divisible by num_generations")
32
+ if completion_len < 0:
33
+ raise ValueError("completion_len must be non-negative")
34
+ if episode_advantages.dim() != 1 or episode_advantages.numel() != batch_size:
35
+ raise ValueError("episode_advantages must have shape [B]")
36
+
37
+ rows: list[TurnCreditRow] = []
38
+ for row_index, (spans, turns) in enumerate(
39
+ zip(turn_spans_per_completion, turn_rewards_per_completion, strict=True)
40
+ ):
41
+ normalized_spans = tuple((int(start), int(end)) for start, end in spans)
42
+ for start, end in normalized_spans:
43
+ if not 0 <= start <= end <= completion_len:
44
+ raise ValueError(
45
+ f"turn span [{start}, {end}) for row {row_index} exceeds completion width "
46
+ f"{completion_len}"
47
+ )
48
+ rows.append(
49
+ TurnCreditRow(
50
+ spans=normalized_spans,
51
+ turns=None if turns is None else tuple(turns),
52
+ )
53
+ )
54
+
55
+ advantages = episode_advantages.new_zeros((batch_size, completion_len))
56
+ for group_start in range(0, batch_size, num_generations):
57
+ group_end = group_start + num_generations
58
+ group = rows[group_start:group_end]
59
+ if any(row.turns is None for row in group):
60
+ for row_index in range(group_start, group_end):
61
+ completion_end = rows[row_index].spans[-1][1] if rows[row_index].spans else 0
62
+ advantages[row_index, :completion_end] = episode_advantages[row_index]
63
+ continue
64
+
65
+ max_turns = max(len(row.turns or ()) for row in group)
66
+ for turn_index in range(max_turns):
67
+ member_indexes = [
68
+ row_index
69
+ for row_index in range(group_start, group_end)
70
+ if turn_index < len(rows[row_index].turns or ())
71
+ and rows[row_index].spans[turn_index][1] > rows[row_index].spans[turn_index][0]
72
+ ]
73
+ if not member_indexes:
74
+ # every member's span for this turn is zero-width (no emitted tokens); an
75
+ # empty turn contributes no advantage and must not skew the group baseline.
76
+ continue
77
+ mean_reward = sum(
78
+ cast(tuple[float, ...], rows[row_index].turns)[turn_index]
79
+ for row_index in member_indexes
80
+ ) / len(member_indexes)
81
+ for row_index in member_indexes:
82
+ row = rows[row_index]
83
+ reward = cast(tuple[float, ...], row.turns)[turn_index]
84
+ start, end = row.spans[turn_index]
85
+ advantages[row_index, start:end] = reward - mean_reward
86
+
87
+ if not bool(torch.isfinite(advantages).all()):
88
+ raise ValueError("per-turn advantages must be finite")
89
+ return advantages
90
+
91
+
92
+ class GRPOPerTurnTrainer(GRPOTrainer):
93
+ """Replace scalar GRPO advantages with aligned per-turn advantages when supplied."""
94
+
95
+ def _generate_and_score_completions(self, inputs: list[dict[str, object]]) -> dict[str, object]:
96
+ output = cast(dict[str, object], super()._generate_and_score_completions(inputs))
97
+ turn_rewards = cast(
98
+ list[list[float] | None],
99
+ [item.get("turn_rewards") for item in inputs],
100
+ )
101
+ if not any(rewards is not None for rewards in turn_rewards):
102
+ return output
103
+ if self.accelerator.num_processes > 1:
104
+ raise NotImplementedError(
105
+ "per-turn GRPO advantages currently support single-process training only; "
106
+ "distributed group centering requires gather-aligned turn metadata"
107
+ )
108
+
109
+ turn_spans = cast(
110
+ list[list[tuple[int, int]] | None],
111
+ [item.get("turn_spans") for item in inputs],
112
+ )
113
+ if any(spans is None for spans in turn_spans):
114
+ raise ValueError("per-turn rollout rows must all include turn_spans")
115
+ aligned_turn_spans = cast(list[list[tuple[int, int]]], turn_spans)
116
+ scalar_advantages = cast(torch.Tensor, output["advantages"])
117
+ if scalar_advantages.dim() != 1:
118
+ raise ValueError("expected TRL scalar advantages with shape [B]")
119
+ completion_ids = cast(torch.Tensor, output["completion_ids"])
120
+ batch_size, completion_len = completion_ids.shape
121
+ if len(inputs) != batch_size:
122
+ raise ValueError(
123
+ f"per-turn metadata has {len(inputs)} row(s) for output batch size {batch_size}"
124
+ )
125
+
126
+ output["advantages"] = build_per_turn_advantages(
127
+ aligned_turn_spans,
128
+ turn_rewards,
129
+ num_generations=(
130
+ self.num_generations if self.model.training else self.num_generations_eval
131
+ ),
132
+ completion_len=completion_len,
133
+ episode_advantages=scalar_advantages,
134
+ ).to(device=scalar_advantages.device, dtype=scalar_advantages.dtype)
135
+ if not getattr(self, "_per_turn_credit_logged", False):
136
+ print("[rl] multi-turn per-turn group-relative credit is active")
137
+ self._per_turn_credit_logged = True
138
+ return output
@@ -9,7 +9,9 @@ from __future__ import annotations
9
9
  import contextlib
10
10
  import faulthandler
11
11
  import json
12
+ import math
12
13
  import os
14
+ import re
13
15
  import sys
14
16
  import threading
15
17
  import time
@@ -235,7 +237,60 @@ def _maybe_attach_gpu_diag(payload: dict, last_gpu_diag_at: float, now: float) -
235
237
  return last_gpu_diag_at
236
238
 
237
239
 
238
- def make_reward_heartbeat_callback():
240
+ _REWARD_METRIC_NAME_DISALLOWED = re.compile(r"[^A-Za-z0-9_.-]")
241
+ _REWARD_METRIC_RESERVED_NAMES = frozenset(
242
+ {
243
+ "reward",
244
+ "reward_last",
245
+ "step",
246
+ "epoch",
247
+ "loss",
248
+ "grad_norm",
249
+ "learning_rate",
250
+ "stage",
251
+ "gpu",
252
+ "diag",
253
+ }
254
+ )
255
+ _REWARD_METRIC_LIMIT = 12
256
+ # names TRAINING.md tells users to judge on: never dropped by the alphabetical cap.
257
+ _REWARD_METRIC_PRIORITY_NAMES = ("success",)
258
+
259
+
260
+ def _bounded_reward_metrics(metrics) -> dict[str, float]:
261
+ if not isinstance(metrics, dict):
262
+ return {}
263
+ surviving: dict[str, float] = {}
264
+ for name, value in metrics.items():
265
+ sanitized_name = _REWARD_METRIC_NAME_DISALLOWED.sub("", str(name))[:64]
266
+ if not sanitized_name or sanitized_name in _REWARD_METRIC_RESERVED_NAMES:
267
+ continue
268
+ try:
269
+ score = float(value)
270
+ except (TypeError, ValueError):
271
+ continue
272
+ if not math.isfinite(score):
273
+ continue
274
+ # distinct source names that sanitize to the same key must not silently overwrite each
275
+ # other; disambiguate with a numeric suffix (kept within the 64-char bound, allowed chars).
276
+ unique_name = sanitized_name
277
+ suffix = 2
278
+ while unique_name in surviving:
279
+ tail = f"_{suffix}"
280
+ unique_name = sanitized_name[: 64 - len(tail)] + tail
281
+ suffix += 1
282
+ surviving[unique_name] = score
283
+ if len(surviving) <= _REWARD_METRIC_LIMIT:
284
+ return dict(sorted(surviving.items()))
285
+ # the cap must not drop metrics users are told to judge on (e.g. success); keep those first,
286
+ # then fill the remaining slots alphabetically.
287
+ priority = [n for n in _REWARD_METRIC_PRIORITY_NAMES if n in surviving]
288
+ remaining = max(0, _REWARD_METRIC_LIMIT - len(priority))
289
+ rest = sorted(n for n in surviving if n not in priority)[:remaining]
290
+ return {n: surviving[n] for n in sorted(priority + rest)}
291
+
292
+
293
+ def make_reward_heartbeat_callback(reward_metrics=None):
239
294
  """Return a TRL callback that streams per-step reward to the HF heartbeat channel."""
240
295
  from transformers import TrainerCallback
241
296
 
@@ -243,6 +298,12 @@ def make_reward_heartbeat_callback():
243
298
  def __init__(self):
244
299
  self.reward_history = []
245
300
  self.last_gpu_diag_at = 0.0
301
+ self.latest_reward_metrics: dict[str, float] = {}
302
+
303
+ def latest_fields(self) -> dict:
304
+ if not self.latest_reward_metrics:
305
+ return {}
306
+ return {"reward_metrics": dict(self.latest_reward_metrics)}
246
307
 
247
308
  def on_log(self, args, state, control, logs=None, **kwargs):
248
309
  if not logs:
@@ -261,6 +322,10 @@ def make_reward_heartbeat_callback():
261
322
  "reward": r,
262
323
  "reward_last": self.reward_history[-8:],
263
324
  }
325
+ latest_metrics = reward_metrics() if callable(reward_metrics) else reward_metrics
326
+ self.latest_reward_metrics = _bounded_reward_metrics(latest_metrics)
327
+ if self.latest_reward_metrics:
328
+ payload["reward_metrics"] = dict(self.latest_reward_metrics)
264
329
  now = time.monotonic()
265
330
  self.last_gpu_diag_at = _maybe_attach_gpu_diag(payload, self.last_gpu_diag_at, now)
266
331
  _w.heartbeat("rl_step", **payload)