freesolo-flash-dev 1.0.7__tar.gz → 1.0.8__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 (366) hide show
  1. freesolo_flash_dev-1.0.8/AB_PLAN.md +45 -0
  2. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/PKG-INFO +1 -1
  3. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/cli/training_doc.py +10 -7
  4. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/cost/analytical.py +16 -5
  5. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/cost/spec.py +8 -9
  6. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/cost/types.py +15 -2
  7. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/diagnostics.py +11 -0
  8. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/engine/vram.py +32 -23
  9. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/engine/worker/gpu_setup.py +74 -3
  10. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/engine/worker/heartbeat.py +93 -27
  11. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/engine/worker/opd.py +32 -1
  12. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/engine/worker/rl.py +110 -62
  13. freesolo_flash_dev-1.0.8/flash/engine/worker/rollout_samples.py +148 -0
  14. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/engine/worker/sft.py +122 -61
  15. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/lora_rank.py +61 -15
  16. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/providers/_poll.py +55 -0
  17. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/providers/runpod/jobs.py +8 -5
  18. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/providers/runpod/train/endpoints.py +10 -3
  19. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/providers/vast/api.py +8 -4
  20. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/runner/__init__.py +38 -2
  21. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/runner/lifecycle.py +7 -0
  22. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/schema/__init__.py +4 -0
  23. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/schema/fields.py +10 -1
  24. freesolo_flash_dev-1.0.8/flash/serve/preflight.py +121 -0
  25. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/server/routes/runs.py +3 -0
  26. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/server/routes/serving.py +26 -10
  27. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/spec.py +34 -0
  28. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/pyproject.toml +2 -2
  29. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_allocator.py +85 -65
  30. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_b200_rollout_opt.py +7 -0
  31. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_chalk_kernels.py +1 -6
  32. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_cli_estimate.py +17 -14
  33. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_cost_estimate.py +31 -6
  34. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_flash_worker.py +9 -10
  35. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_gpus.py +2 -3
  36. freesolo_flash_dev-1.0.8/tests/test_grpo_cudagraphs.py +101 -0
  37. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_grpo_params.py +76 -0
  38. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_jobs.py +90 -0
  39. freesolo_flash_dev-1.0.8/tests/test_multi_gpu_gate.py +83 -0
  40. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_opd.py +47 -0
  41. freesolo_flash_dev-1.0.8/tests/test_rollout_samples.py +606 -0
  42. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_server_api.py +253 -0
  43. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_server_envs_coverage.py +63 -2
  44. freesolo_flash_dev-1.0.8/tests/test_sft_chunked_nll.py +292 -0
  45. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_spec_and_validation.py +51 -0
  46. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_train_context_preflight.py +24 -1
  47. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_vast_api.py +12 -3
  48. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_worker_init_heartbeat.py +34 -0
  49. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/uv.lock +1 -1
  50. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/.claude/skills/verify/SKILL.md +0 -0
  51. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/.dockerignore +0 -0
  52. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/.env.example +0 -0
  53. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/.github/workflows/auto-rebake.yml +0 -0
  54. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/.github/workflows/bake-kernel-cache.yml +0 -0
  55. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/.github/workflows/ci.yml +0 -0
  56. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/.github/workflows/main-source-guard.yml +0 -0
  57. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/.github/workflows/notify-tests-repo.yml +0 -0
  58. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/.github/workflows/publish-dev.yml +0 -0
  59. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/.github/workflows/publish-image.yml +0 -0
  60. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/.github/workflows/publish.yml +0 -0
  61. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/.github/workflows/version-parity.yml +0 -0
  62. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/.github/workflows/worker-image.yml +0 -0
  63. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/.gitignore +0 -0
  64. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/Dockerfile +0 -0
  65. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/Dockerfile.worker +0 -0
  66. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/LICENSE +0 -0
  67. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/README.md +0 -0
  68. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/build/kernel_cache/.gitignore +0 -0
  69. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/build/kernel_cache/.keep +0 -0
  70. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/docker/Dockerfile.kernelcache +0 -0
  71. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/docker/Dockerfile.kernelcache.relayer +0 -0
  72. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/docker/bake_kernel_cache.py +0 -0
  73. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/docker/bake_pod_entry.py +0 -0
  74. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/docker/kernel_fingerprint.py +0 -0
  75. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/docker/make_rp_handler.py +0 -0
  76. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/__init__.py +0 -0
  77. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/_channel.py +0 -0
  78. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/_fileio.py +0 -0
  79. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/_logging.py +0 -0
  80. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/_update_check.py +0 -0
  81. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/adapter_artifacts.py +0 -0
  82. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/catalog.py +0 -0
  83. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/cli/__init__.py +0 -0
  84. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/cli/__main__.py +0 -0
  85. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/cli/_tty.py +0 -0
  86. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/cli/commands.py +0 -0
  87. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/cli/env_setup.py +0 -0
  88. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/cli/envpush.py +0 -0
  89. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/cli/render.py +0 -0
  90. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/client/__init__.py +0 -0
  91. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/client/config.py +0 -0
  92. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/client/http.py +0 -0
  93. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/client/runtime_secrets.py +0 -0
  94. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/client/specs.py +0 -0
  95. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/cost/__init__.py +0 -0
  96. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/cost/facts.py +0 -0
  97. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/engine/__init__.py +0 -0
  98. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/engine/accounting.py +0 -0
  99. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/engine/chalk_kernels.py +0 -0
  100. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/engine/multiturn_reward_scoring.py +0 -0
  101. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/engine/multiturn_rollout.py +0 -0
  102. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/engine/recipe.py +0 -0
  103. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/engine/steps.py +0 -0
  104. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/engine/structured_outputs.py +0 -0
  105. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/engine/worker/__init__.py +0 -0
  106. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/engine/worker/__main__.py +0 -0
  107. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/engine/worker/_pkg.py +0 -0
  108. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/engine/worker/adapter.py +0 -0
  109. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/engine/worker/decoding.py +0 -0
  110. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/engine/worker/finalize.py +0 -0
  111. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/engine/worker/grpo.py +0 -0
  112. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/engine/worker/grpo_multimodal.py +0 -0
  113. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/engine/worker/grpo_perturn_trainer.py +0 -0
  114. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/engine/worker/hf.py +0 -0
  115. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/engine/worker/kernel_warmup.py +0 -0
  116. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/engine/worker/lora.py +0 -0
  117. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/engine/worker/opd_gkd.py +0 -0
  118. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/engine/worker/opd_vllm.py +0 -0
  119. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/engine/worker/packing.py +0 -0
  120. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/engine/worker/perf/__init__.py +0 -0
  121. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/engine/worker/perf/attn.py +0 -0
  122. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/engine/worker/perf/diagnostics.py +0 -0
  123. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/engine/worker/perf/lifecycle.py +0 -0
  124. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/engine/worker/perf/liger.py +0 -0
  125. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/engine/worker/perf/loraplus.py +0 -0
  126. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/engine/worker/perf/memory.py +0 -0
  127. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/engine/worker/rng.py +0 -0
  128. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/engine/worker/teacher.py +0 -0
  129. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/engine/worker/tokenizer_align.py +0 -0
  130. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/engine/worker/wandb_log.py +0 -0
  131. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/engine/worker_entrypoint.py +0 -0
  132. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/envs/__init__.py +0 -0
  133. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/envs/adapter.py +0 -0
  134. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/envs/archive.py +0 -0
  135. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/envs/archive_policy.py +0 -0
  136. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/envs/base.py +0 -0
  137. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/envs/loader.py +0 -0
  138. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/envs/pull.py +0 -0
  139. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/envs/registry.py +0 -0
  140. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/multimodal.py +0 -0
  141. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/opd_retry_contract.py +0 -0
  142. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/providers/__init__.py +0 -0
  143. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/providers/_auth.py +0 -0
  144. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/providers/_deadline.py +0 -0
  145. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/providers/_hf_artifacts.py +0 -0
  146. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/providers/_hf_retry.py +0 -0
  147. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/providers/_http.py +0 -0
  148. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/providers/_instance.py +0 -0
  149. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/providers/_instance_bootstrap.py +0 -0
  150. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/providers/_instance_poll.py +0 -0
  151. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/providers/_instance_provider.py +0 -0
  152. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/providers/_worker.py +0 -0
  153. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/providers/allocator.py +0 -0
  154. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/providers/base.py +0 -0
  155. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/providers/lambdalabs/__init__.py +0 -0
  156. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/providers/lambdalabs/api.py +0 -0
  157. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/providers/lambdalabs/auth.py +0 -0
  158. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/providers/lambdalabs/gpus.py +0 -0
  159. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/providers/lambdalabs/jobs/__init__.py +0 -0
  160. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/providers/lambdalabs/jobs/builders.py +0 -0
  161. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/providers/lambdalabs/preflight.py +0 -0
  162. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/providers/lambdalabs/pricing.py +0 -0
  163. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/providers/preflight.py +0 -0
  164. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/providers/realized.py +0 -0
  165. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/providers/runpod/__init__.py +0 -0
  166. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/providers/runpod/api.py +0 -0
  167. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/providers/runpod/auth.py +0 -0
  168. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/providers/runpod/cost.py +0 -0
  169. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/providers/runpod/gpus.py +0 -0
  170. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/providers/runpod/keys.py +0 -0
  171. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/providers/runpod/preflight.py +0 -0
  172. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/providers/runpod/preload.py +0 -0
  173. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/providers/runpod/pricing.py +0 -0
  174. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/providers/runpod/slots.py +0 -0
  175. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/providers/runpod/train/__init__.py +0 -0
  176. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/providers/vast/__init__.py +0 -0
  177. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/providers/vast/auth.py +0 -0
  178. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/providers/vast/jobs/__init__.py +0 -0
  179. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/providers/vast/jobs/builders.py +0 -0
  180. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/providers/vast/preflight.py +0 -0
  181. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/providers/vast/pricing.py +0 -0
  182. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/py.typed +0 -0
  183. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/runner/checkpoints.py +0 -0
  184. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/runner/deploy.py +0 -0
  185. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/runner/verified_revisions.py +0 -0
  186. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/serve/__init__.py +0 -0
  187. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/serve/deploy.py +0 -0
  188. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/serve/export.py +0 -0
  189. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/serve/pricing.py +0 -0
  190. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/serve/urls.py +0 -0
  191. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/server/__init__.py +0 -0
  192. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/server/__main__.py +0 -0
  193. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/server/_deps.py +0 -0
  194. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/server/_internal_client.py +0 -0
  195. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/server/_locks.py +0 -0
  196. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/server/_runtime.py +0 -0
  197. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/server/app.py +0 -0
  198. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/server/auth.py +0 -0
  199. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/server/billing.py +0 -0
  200. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/server/billing_retry.py +0 -0
  201. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/server/checkpoints.py +0 -0
  202. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/server/db.py +0 -0
  203. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/server/environment_registry.py +0 -0
  204. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/server/envs.py +0 -0
  205. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/server/reconcile.py +0 -0
  206. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/server/repo_cleanup.py +0 -0
  207. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/server/routes/__init__.py +0 -0
  208. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/server/routes/envs.py +0 -0
  209. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/server/routes/meta.py +0 -0
  210. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/flash/server/run_registry.py +0 -0
  211. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/infisical-entrypoint.sh +0 -0
  212. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/scripts/build_dev_dist.py +0 -0
  213. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/__init__.py +0 -0
  214. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/_helpers/__init__.py +0 -0
  215. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/_helpers/runner.py +0 -0
  216. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/_helpers/specs.py +0 -0
  217. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/_helpers/vast.py +0 -0
  218. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/conftest.py +0 -0
  219. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/fixtures/math_eval.jsonl +0 -0
  220. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/fixtures/math_train.jsonl +0 -0
  221. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/live/__init__.py +0 -0
  222. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/live/conftest.py +0 -0
  223. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/live/test_lambda_live.py +0 -0
  224. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/live/test_runpod_live.py +0 -0
  225. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/live/test_vast_live.py +0 -0
  226. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_accounting_coverage.py +0 -0
  227. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_agent_flash_cli_contract.py +0 -0
  228. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_algorithms.py +0 -0
  229. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_async_uploads.py +0 -0
  230. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_backend_jobspec_contract.py +0 -0
  231. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_base_model_provenance.py +0 -0
  232. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_billing_retry.py +0 -0
  233. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_cancel_remote.py +0 -0
  234. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_catalog_consistency.py +0 -0
  235. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_charge_pricing.py +0 -0
  236. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_checkpoints.py +0 -0
  237. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_cli_commands.py +0 -0
  238. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_cli_commands_coverage.py +0 -0
  239. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_cli_errors.py +0 -0
  240. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_cli_help.py +0 -0
  241. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_cli_main_coverage.py +0 -0
  242. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_cli_managed.py +0 -0
  243. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_cli_render_coverage.py +0 -0
  244. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_cli_render_theme.py +0 -0
  245. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_client.py +0 -0
  246. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_client_server_integration.py +0 -0
  247. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_client_stream_reads.py +0 -0
  248. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_config_overrides.py +0 -0
  249. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_controlled_experiment_repairs.py +0 -0
  250. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_cost_analytical.py +0 -0
  251. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_cost_equation.py +0 -0
  252. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_cost_hardware.py +0 -0
  253. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_cost_models.py +0 -0
  254. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_cost_rewards.py +0 -0
  255. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_deploy_latency.py +0 -0
  256. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_dev_channel.py +0 -0
  257. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_disk_gb.py +0 -0
  258. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_endpoint_name.py +0 -0
  259. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_env_cache_evict.py +0 -0
  260. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_env_delete.py +0 -0
  261. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_env_download.py +0 -0
  262. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_env_download_api.py +0 -0
  263. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_env_no_install.py +0 -0
  264. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_env_publish.py +0 -0
  265. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_env_pull.py +0 -0
  266. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_env_pull_coverage.py +0 -0
  267. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_env_pull_loader_coverage.py +0 -0
  268. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_env_pull_managed_control_plane.py +0 -0
  269. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_env_push.py +0 -0
  270. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_env_rate_limit_resolve.py +0 -0
  271. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_envs_coverage.py +0 -0
  272. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_export.py +0 -0
  273. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_flash_mvp.py +0 -0
  274. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_flashinfer_cache_dirs.py +0 -0
  275. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_github_urlopen_retry.py +0 -0
  276. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_grpo_mask_aware.py +0 -0
  277. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_grpo_perturn.py +0 -0
  278. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_grpo_sleep_gate.py +0 -0
  279. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_health_liveness.py +0 -0
  280. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_hf_retry.py +0 -0
  281. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_idle_endpoint_reaper.py +0 -0
  282. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_instance_bootstrap_coverage.py +0 -0
  283. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_internal_client.py +0 -0
  284. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_kernel_cache.py +0 -0
  285. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_kernel_fingerprint.py +0 -0
  286. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_kv_util.py +0 -0
  287. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_lambda_api_coverage.py +0 -0
  288. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_lambda_runner.py +0 -0
  289. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_logging.py +0 -0
  290. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_login_perms.py +0 -0
  291. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_lora_rank_coverage.py +0 -0
  292. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_lora_rank_preflight.py +0 -0
  293. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_managed_hf_repo.py +0 -0
  294. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_metrics_schema_agent_contract.py +0 -0
  295. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_mig_guard.py +0 -0
  296. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_multimodal_input_grads.py +0 -0
  297. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_multimodal_training.py +0 -0
  298. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_multiturn_image_rollout.py +0 -0
  299. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_multiturn_per_turn_reward.py +0 -0
  300. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_multiturn_rollout.py +0 -0
  301. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_multiturn_rollout_coverage.py +0 -0
  302. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_multiturn_rollout_records.py +0 -0
  303. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_multiturn_rollout_request_policy.py +0 -0
  304. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_named_reward_metrics.py +0 -0
  305. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_oom_escalate_gpu.py +0 -0
  306. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_opd_full_state_resume.py +0 -0
  307. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_opd_resume_safety.py +0 -0
  308. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_opd_vllm.py +0 -0
  309. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_open_model_policy.py +0 -0
  310. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_orchestrator_flash.py +0 -0
  311. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_packing.py +0 -0
  312. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_packing_coverage.py +0 -0
  313. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_poll_helpers.py +0 -0
  314. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_preflight.py +0 -0
  315. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_provider_preflight_coverage.py +0 -0
  316. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_provider_routing.py +0 -0
  317. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_provider_teardown_robustness.py +0 -0
  318. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_providers_symmetry.py +0 -0
  319. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_realized_cost.py +0 -0
  320. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_repo_cleanup.py +0 -0
  321. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_resolve_params_b.py +0 -0
  322. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_resume_on_retry.py +0 -0
  323. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_runmgmt.py +0 -0
  324. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_runpod_api_delete.py +0 -0
  325. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_runpod_key_fingerprint.py +0 -0
  326. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_runpod_key_waterfall.py +0 -0
  327. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_runpod_slots.py +0 -0
  328. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_serve.py +0 -0
  329. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_server_app_coverage.py +0 -0
  330. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_server_auth_singleflight.py +0 -0
  331. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_server_billing.py +0 -0
  332. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_server_checkpoints_coverage.py +0 -0
  333. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_server_db.py +0 -0
  334. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_server_locks_coverage.py +0 -0
  335. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_serving_context_preflight.py +0 -0
  336. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_serving_contract.py +0 -0
  337. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_serving_pricing_coverage.py +0 -0
  338. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_serving_schema_coverage.py +0 -0
  339. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_sft_activation_shaping.py +0 -0
  340. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_sft_example_selection.py +0 -0
  341. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_sft_gc_off.py +0 -0
  342. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_sft_max_context.py +0 -0
  343. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_sft_tokenize_cache.py +0 -0
  344. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_single_turn_multimodal_grpo.py +0 -0
  345. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_structured_outputs.py +0 -0
  346. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_thinking_config.py +0 -0
  347. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_training_controls.py +0 -0
  348. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_update_check.py +0 -0
  349. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_vast_api_coverage.py +0 -0
  350. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_vast_offers.py +0 -0
  351. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_vast_provider_coverage.py +0 -0
  352. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_vast_runner.py +0 -0
  353. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_verified_revisions.py +0 -0
  354. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_verifiers.py +0 -0
  355. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_version.py +0 -0
  356. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_vl_warmstart_adapter_keys.py +0 -0
  357. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_wandb_log_coverage.py +0 -0
  358. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_wandb_naming.py +0 -0
  359. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_warmstart_adapter_download.py +0 -0
  360. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_warmstart_cross_repo.py +0 -0
  361. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_weight_cache.py +0 -0
  362. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_worker_dryrun.py +0 -0
  363. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_worker_hardexit.py +0 -0
  364. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_worker_image.py +0 -0
  365. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_worker_stack.py +0 -0
  366. {freesolo_flash_dev-1.0.7 → freesolo_flash_dev-1.0.8}/tests/test_worker_thinking.py +0 -0
@@ -0,0 +1,45 @@
1
+ # GRPO decode-only CUDA graph A/B plan
2
+
3
+ ## Objective
4
+
5
+ Measure whether decode-only CUDA graphs speed up the colocated GRPO rollout engine without changing training behavior. This plan does not authorize a paid run.
6
+
7
+ ## Frozen comparison
8
+
9
+ - Control: `origin/dev` at `8fdfc8580b9058d8c40289d4fa1a2e6e3b7bced2`. On RTX 5090/sm120 with vLLM 0.19.1, this selects `enforce_eager=True`.
10
+ - Treatment: the committed head of `perf/grpo-cudagraphs`. On the same GPU and vLLM version, this selects `enforce_eager=False` with `mode=0` and `cudagraph_mode=FULL_DECODE_ONLY`.
11
+ - Hardware: two separate, matched RunPod RTX 5090 workers from the same GPU offer, image, region, CPU/RAM class, and storage configuration. Do not substitute unmatched cards between arms.
12
+ - Model: `Qwen/Qwen3.5-0.8B`, which fits within an RTX 4090-class memory budget.
13
+ - Environment: the real `entropy-R1` hub environment, pinned to one immutable `freesolo-co/environment-hub` commit before launch.
14
+ - Seed: 42 for both arms.
15
+ - Budget: 30 optimizer updates after a two-update exact-path smoke. Use the same prompts per step, group size, maximum context, maximum completion length, LoRA settings, optimizer settings, reward implementation, dataset order, and checkpoint schedule.
16
+ - Concurrency: launch both paid arms together only after both two-update smokes pass. Do not reuse a worker across arms.
17
+
18
+ ## Preflight and token-parity gate
19
+
20
+ 1. Resolve and record the exact Flash commit, environment-hub commit, model revision, container digest, vLLM version, CUDA/driver versions, and RunPod machine identifiers for each arm.
21
+ 2. Run the same fixed prompt batch through both immutable rollout engines with greedy decoding and the same maximum token count.
22
+ 3. Require identical token IDs, finish reasons, and decoded text for every fixed prompt. Any difference is a correctness failure and stops the A/B.
23
+ 4. Run two GRPO updates per arm. Require successful graph capture or vLLM's eager fallback, finite loss/reward values, and no OOM before scaling to 30 updates.
24
+
25
+ ## Metrics
26
+
27
+ Measure rollout work separately from training work where telemetry permits.
28
+
29
+ - Primary performance: generated rollout tokens per rollout-engine second, excluding engine startup, graph capture, checkpointing, reward evaluation, and optimizer time.
30
+ - Secondary performance: median rollout wall time per update and end-to-end median step wall time. Report graph-capture startup time separately rather than amortizing it away.
31
+ - Correctness: fixed-prompt rollout token parity, reward curve, training loss curve, completion length, finish-reason distribution, and invalid/empty completion rate.
32
+ - Safety: peak allocated and reserved GPU memory, OOM count, graph-capture failures, eager-fallback events, worker restarts, and failed updates.
33
+
34
+ ## Decision thresholds
35
+
36
+ The expected direction is faster treatment decode with unchanged learning behavior.
37
+
38
+ - Performance works: treatment improves steady-state rollout tokens/second by at least 10% over control. A 10% to 40% improvement is the expected range. Confirm the direction with at least a 9% reduction in median rollout wall time over updates 3 through 30.
39
+ - End-to-end value: treatment reduces median total step wall time by at least 5%. Report a decode-only win below this threshold as technically real but operationally marginal.
40
+ - Reward parity: treatment reward AUC over the 30 matched updates must remain within 5% relative or 0.02 absolute of control, whichever tolerance is larger. There must be no sustained three-update reward regression larger than 0.05 absolute.
41
+ - Loss parity: treatment loss AUC must remain within 5% relative of control, with finite values at every update and no new divergence pattern.
42
+ - Rollout parity: the fixed greedy prompt batch must have exact token-ID parity. During stochastic training, completion length, finish-reason, and empty/invalid-rate differences must each remain within 5% relative or one sample, whichever is larger.
43
+ - Memory and reliability: no OOM, no worker restart, no failed update, and treatment peak reserved memory no more than 5% above control after graph capture.
44
+
45
+ The treatment passes only if all correctness and reliability gates pass and the primary performance threshold is met. If vLLM falls back to eager, report the fallback and classify the performance result as inconclusive rather than a decode-graph success.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: freesolo-flash-dev
3
- Version: 1.0.7
3
+ Version: 1.0.8
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
@@ -277,13 +277,16 @@ spending another GPU run:
277
277
  - **Judge the trend, not a single number.** The proof of training is the curve:
278
278
  loss falling (SFT) or `reward` rising over steps (GRPO). Record the base/early
279
279
  value and the final value. A flat or noisy trend with no improvement is not success.
280
- - **Read the model's outputs, not just the metrics.** A rising reward can come from
281
- reward-hacking or a degenerate output the reward still credits — metrics alone never
282
- establish that the model got better. Flash does not expose training-time rollouts
283
- through the CLI (`flash log` gives you the metric trend and the worker's console/error
284
- logs, not the sampled generations), so to read real outputs **deploy the adapter and
285
- probe it**: `flash deploy <run-id>` then `flash chat <run-id> -m "..."` on at least a
286
- few real inputs, including ones it should get wrong.
280
+ - **Read the model's outputs, not just the metrics.** A rising reward (or falling loss)
281
+ can come from reward-hacking or a degenerate output the metric still credits — metrics
282
+ alone never establish that the model got better. For GRPO and OPD, `flash log` surfaces a
283
+ handful of full (untruncated) sample completions at the heartbeat cadence GRPO shows
284
+ each completion's reward, OPD its distillation loss with the first sample-bearing update
285
+ forced through, so you can catch skipped reasoning or a parroted prompt placeholder by
286
+ step 1-2. These are bounded diagnostics, not every rollout or a held-out
287
+ evaluation, so still **deploy the adapter and probe it**: `flash deploy <run-id>` then
288
+ `flash chat <run-id> -m "..."` on at least a few real inputs, including ones it should
289
+ get wrong.
287
290
 
288
291
  ```bash
289
292
  flash status <run-id> # state + accrued cost
@@ -166,7 +166,9 @@ def seconds_per_step(config: RunConfig, gpu: str) -> float:
166
166
  # sequence (see _opd_step_shape), not completion-only, or long-prompt opd is underquoted.
167
167
  completions, seq_tokens = _opd_step_shape(n)
168
168
  gen_s = (GRPO_GEN_FLOPS_PER_TOKEN_PER_PARAM * params * seq_tokens) / (peak * MFU_DECODE)
169
- update_s = (OPD_UPDATE_FLOPS_PER_TOKEN_PER_PARAM * params * seq_tokens) / (peak * update_mfu)
169
+ update_s = (OPD_UPDATE_FLOPS_PER_TOKEN_PER_PARAM * params * seq_tokens) / (
170
+ peak * update_mfu
171
+ )
170
172
  teacher_lat = teacher_seconds_per_completion()
171
173
  # run_opd's primary path scores a step's completions CONCURRENTLY over Fireworks with a fan-out
172
174
  # cap of the step's OWN completion count (prompts_per_step * group_size, opd.py Phase 2), so
@@ -191,6 +193,7 @@ def seconds_per_step(config: RunConfig, gpu: str) -> float:
191
193
  ) # ceil: a partial wave still costs one latency
192
194
  return overhead + gen_s + reward_s + update_s
193
195
 
196
+
194
197
  def sft_seconds_for_tokens(config: RunConfig, gpu: str, train_tokens: float) -> float:
195
198
  """SFT steady-state wall time for an actual token count on ``gpu``."""
196
199
  n = config.normalized()
@@ -276,7 +279,11 @@ def _notes(
276
279
  def estimate_cost(config: RunConfig, *, wall_cap_s: float = DEFAULT_WALL_CAP_S) -> CostEstimate:
277
280
  """Deterministic pre-flight cost calculation."""
278
281
  # Billing cap: mirror the runner's max(60, max_wall_seconds) floor so a sub-60s cap isn't underpriced.
279
- cap_s = max(60.0, float(config.max_wall_seconds)) if config.max_wall_seconds is not None else wall_cap_s
282
+ cap_s = (
283
+ max(60.0, float(config.max_wall_seconds))
284
+ if config.max_wall_seconds is not None
285
+ else wall_cap_s
286
+ )
280
287
  # Vast market duration filter: price against offers that outlast the run, using the SAME semantics
281
288
  # ``usable_offers`` applies at LAUNCH (not the 60s-floored billing cap_s) — a non-positive wall means
282
289
  # NO filter, a positive one is floored at 60s by usable_offers itself:
@@ -331,7 +338,9 @@ def estimate_cost(config: RunConfig, *, wall_cap_s: float = DEFAULT_WALL_CAP_S)
331
338
  compile_s = compile_seconds(config, gpu)
332
339
  raw_train = compile_s + config.steps * sps + required_save_s
333
340
  if not config.is_grpo and config.train_tokens is not None:
334
- raw_train = compile_s + sft_seconds_for_tokens(config, gpu, config.train_tokens) + required_save_s
341
+ raw_train = (
342
+ compile_s + sft_seconds_for_tokens(config, gpu, config.train_tokens) + required_save_s
343
+ )
335
344
  sps = raw_train / config.steps
336
345
 
337
346
  # The cap is on total elapsed wall; setup is reported but not billed, so only training
@@ -367,9 +376,11 @@ def estimate_cost(config: RunConfig, *, wall_cap_s: float = DEFAULT_WALL_CAP_S)
367
376
  train_seconds=train,
368
377
  wall_clock_seconds=wall,
369
378
  wall_capped=wall_capped,
379
+ gpu_count=config.gpu_count,
370
380
  # total_usd is the customer gpu charge. the platform-owned teacher spend is itemized
371
- # only as a diagnostic and is not passed through to the customer.
372
- total_usd=train / 3600.0 * hourly,
381
+ # only as a diagnostic and is not passed through to the customer. an n-card job occupies
382
+ # n cards for the billed training wall, so the charge scales linearly with gpu_count.
383
+ total_usd=train / 3600.0 * hourly * config.gpu_count,
373
384
  teacher_api_usd=teacher_api_usd,
374
385
  notes=_notes(config, raw_train, wall_capped, cap_s),
375
386
  )
@@ -88,19 +88,17 @@ def _on_policy_prompts_per_step(spec, examples: int) -> int:
88
88
  def _sft_realized_batch(spec) -> int:
89
89
  from flash.catalog import resolve_vocab_size
90
90
  from flash.engine.recipe import RECIPE
91
- from flash.engine.vram import resolve_params_b, sft_logits_fused, sft_realized_batch
91
+ from flash.engine.vram import sft_chunked_nll_enabled, sft_realized_batch
92
92
 
93
93
  t = spec.train
94
94
  requested_batch = int(t.batch_size) if t.batch_size is not None else RECIPE.sft.effective_batch
95
95
  sft_seq = _sft_seq_len(spec)
96
- # Resolve params_b via the shared helper (catalog stat else HF safetensors for an open model)
97
- # the SAME resolution the worker's run_sft uses. The fused-CE decision (and thus the big-vocab
98
- # micro-batch cap) hinges on the >=3B threshold, so an uncataloged >=3B model must not be priced
99
- # as <3B (which would flip fused off, change the realized batch via the cap, and misprice the
100
- # step count). Best-effort: no network -> None -> the prior <3B (cap-on) behavior.
101
- sft_fused = sft_logits_fused(
102
- resolve_params_b(spec.model, revision=spec.model_revision), sft_seq
103
- )
96
+ # mirror the worker's validated chunked-nll model set so pricing uses the same realized batch.
97
+ # every allowlisted model is a gdn hybrid; at packable context lengths the worker may force
98
+ # per-device=1, so price the requested batch rather than the larger chunked-nll rounded batch.
99
+ sft_fused = sft_chunked_nll_enabled(spec.model)
100
+ if sft_fused and sft_seq <= 16_384:
101
+ return requested_batch
104
102
  return sft_realized_batch(
105
103
  requested_batch,
106
104
  seq_len=sft_seq,
@@ -167,6 +165,7 @@ def runconfig_from_spec(spec) -> RunConfig:
167
165
  exact_type=g.exact_type,
168
166
  model_revision=spec.model_revision,
169
167
  disk_gb=float(getattr(g, "disk_gb", 0.0) or 0.0),
168
+ gpu_count=g.count,
170
169
  max_wall_seconds=g.max_wall_seconds,
171
170
  environment=spec.environment.id or None,
172
171
  save_at_steps=t.save_at_steps,
@@ -46,6 +46,9 @@ class RunConfig:
46
46
  # Spec gpu.disk_gb, carried so an exact-auto quote allocates at the run's real disk floor (parity
47
47
  # with the launch allocate call), keeping the persisted quote aligned with the pinned hardware.
48
48
  disk_gb: float = 0.0
49
+ # Spec gpu.count: cards the job occupies. total cost scales linearly with it (n cards for the
50
+ # billed training wall); 1 = the historical single-gpu quote.
51
+ gpu_count: int = 1
49
52
 
50
53
  def __post_init__(self) -> None:
51
54
  object.__setattr__(self, "method", normalize_algorithm(self.method))
@@ -71,6 +74,14 @@ class RunConfig:
71
74
  object.__setattr__(self, "model_revision", self.model_revision.strip())
72
75
  if self.steps < 1:
73
76
  raise ValueError(f"steps must be >= 1, got {self.steps}")
77
+ if isinstance(self.gpu_count, bool) or not isinstance(self.gpu_count, int):
78
+ raise TypeError("gpu_count must be an integer")
79
+ if self.gpu_count < 1:
80
+ raise ValueError(f"gpu_count must be >= 1, got {self.gpu_count}")
81
+ # upper bound mirrors GpuSpec's 1..8 so a direct RunConfig(gpu_count=...) cannot price a
82
+ # count the spec layer would reject.
83
+ if self.gpu_count > 8:
84
+ raise ValueError(f"gpu_count must be <= 8, got {self.gpu_count}")
74
85
  # Reject 0/negative positive-only knobs (bogus quote). max_wall_seconds is NOT here: the
75
86
  # runner floors it to max(60, ...) and estimate_cost mirrors that, so a non-positive cap
76
87
  # is accepted (floored to 60s), not rejected.
@@ -173,6 +184,8 @@ class CostEstimate:
173
184
  wall_clock_seconds: float
174
185
  wall_capped: bool
175
186
  total_usd: float
187
+ # cards the job occupies; total_usd already reflects n-card billing. 1 = single-gpu quote.
188
+ gpu_count: int = 1
176
189
  # opd only: external fireworks teacher token spend (0.0 for sft/grpo). billed by fireworks
177
190
  # to the platform-managed teacher key (users don't supply one), tracked separately from the
178
191
  # platform-billed gpu charge, so it is not part of total_usd and is shown as its own itemized
@@ -192,9 +205,9 @@ class CostEstimate:
192
205
  """Multi-line itemized breakdown for CLI output."""
193
206
  lines = [
194
207
  f"Run : {self.model_id} [{self.method.upper()}, {self.steps} steps]",
195
- f"GPU : {self.gpu} "
208
+ f"GPU : {f'{self.gpu_count}x ' if self.gpu_count > 1 else ''}{self.gpu} "
196
209
  f"({self.gpu_vram_gb} GB; run needs >= {self.required_vram_gb} GB) "
197
- f"@ ${self.gpu_hourly_usd:.2f}/hr",
210
+ f"@ ${self.gpu_hourly_usd:.2f}/hr{' per card' if self.gpu_count > 1 else ''}",
198
211
  f"Setup : {self.setup_seconds / 60:.1f} min (cold start: boot + deps + model load"
199
212
  + (" + vLLM init" if self.method == "grpo" else "")
200
213
  + "; not billed)",
@@ -39,3 +39,14 @@ def sanitize_diagnostic(value: Any, *, limit: int = 2000) -> str:
39
39
  text = _BEARER_RE.sub("Bearer <redacted>", text)
40
40
  text = _SECRET_KEY_RE.sub(lambda match: f"{match.group(1)}{match.group(2)}<redacted>", text)
41
41
  return text[: max(0, int(limit))]
42
+
43
+
44
+ def neutralize_control_chars(value: Any) -> str:
45
+ """Escape terminal control characters while preserving newlines as separators."""
46
+ text = str(value)
47
+ return "".join(
48
+ char
49
+ if char == "\n" or 0x20 <= ord(char) < 0x7F or ord(char) >= 0xA0
50
+ else f"\\x{ord(char):02x}"
51
+ for char in text
52
+ )
@@ -228,17 +228,27 @@ _VOCAB_DEFAULT = 248_320
228
228
  _LOGITS_BUDGET_GB = 6.0
229
229
  # 16 B/elem: fp32 logits+grad + bf16 logits+grad + CE temp. 8 B/elem under-counts (live OOM confirmed).
230
230
  _SFT_LOGITS_BYTES_PER_ELEM = 16.0
231
- # Single source of truth for the SFT fused-CE gate. Keep the historical constant names because
232
- # engine.worker.perf and tests import them.
231
+ _SFT_CHUNKED_NLL_TOKENS = 256
232
+ # shared thresholds for the independent liger and worker memory gates.
233
233
  _LIGER_MIN_PARAMS_B = 3.0
234
234
  _LIGER_LONG_CTX_TOKENS = 2048
235
235
 
236
+ # trl 1.6 chunked_nll is validated against these model families. other models keep plain nll
237
+ # until their output-head and backbone traversal are covered by the same parity tests.
238
+ _SFT_CHUNKED_NLL_MODELS = frozenset(
239
+ {
240
+ "Qwen/Qwen3.5-0.8B",
241
+ "Qwen/Qwen3.5-2B",
242
+ "Qwen/Qwen3.5-4B",
243
+ "Qwen/Qwen3.5-9B",
244
+ "Qwen/Qwen3.6-35B-A3B",
245
+ }
246
+ )
236
247
 
237
- def sft_logits_fused(params_b: float | None, seq_len: int) -> bool:
238
- """True when the worker fuses SFT cross-entropy (>=3B model OR >=2048-token context)."""
239
- if seq_len >= _LIGER_LONG_CTX_TOKENS:
240
- return True
241
- return (params_b or 0.0) >= _LIGER_MIN_PARAMS_B
248
+
249
+ def sft_chunked_nll_enabled(model_id: str) -> bool:
250
+ """whether the sft worker uses trl's dense-logit-free chunked nll path."""
251
+ return model_id in _SFT_CHUNKED_NLL_MODELS
242
252
 
243
253
 
244
254
  def sft_logits_per_device_cap(seq_len: int, vocab: int) -> int:
@@ -384,14 +394,15 @@ def estimate_vram_gb(
384
394
  dense_image_logits = (seq_len * 4 + completion * 8) * vocab / 1e9
385
395
  logits = max(chunked_logits, dense_image_logits)
386
396
  return base + rollout + activations + logits
387
- # Actual TRL SFT keeps fused CE disabled (see worker/sft.py), so dense logits materialize even
388
- # for long-context / >=3B models. Callers can pass sft_fused_ce=True only for theoretical
389
- # comparisons; the default must mirror the worker.
397
+ # direct callers default to the conservative plain-nll estimate. model_required_vram_gb passes
398
+ # the worker's validated chunked-nll decision explicitly when it knows the model identity.
390
399
  fused = False if sft_fused_ce is None else bool(sft_fused_ce)
391
400
  pd = sft_per_device(batch_size, seq_len=seq_len, vocab=vocab, fused=fused)
392
401
  activations = _ACT_COEF * pd * (seq_len / 1024.0) * width
393
- # Don't clamp to budget: pd=1 is irreducible and the logits can exceed the budget at near-2048 ctx.
394
- logits = 0.0 if fused else pd * seq_len * vocab * _SFT_LOGITS_BYTES_PER_ELEM / 1e9
402
+ # plain nll retains every sequence position. chunked nll retains at most one 256-token vocab
403
+ # projection at a time, independent of micro-batch and context once the chunk is full.
404
+ projected_tokens = min(pd * seq_len, _SFT_CHUNKED_NLL_TOKENS) if fused else pd * seq_len
405
+ logits = projected_tokens * vocab * _SFT_LOGITS_BYTES_PER_ELEM / 1e9
395
406
  return base + activations + logits
396
407
 
397
408
 
@@ -465,10 +476,10 @@ def sft_gc_off_peak_gb(
465
476
  lora_rank: int = 32,
466
477
  quant: str = "bf16",
467
478
  ) -> float:
468
- """Estimated peak VRAM (GB) for a FUSED-CE LoRA SFT step with gradient checkpointing OFF: the
469
- resident weights + optimizer/base + the no-recompute activations held across ALL ``num_layers``.
470
- Fused CE (chalk FLCE) is assumed, so there is no ``[B, T, vocab]`` logits term (the thing that
471
- made GC-off impossible at a 248k vocab). Unknown architecture dims -> ``inf`` (caller keeps GC on).
479
+ """Estimated peak VRAM (GB) for a dense-logit-free LoRA SFT step with gradient checkpointing OFF:
480
+ the resident weights + optimizer/base + the no-recompute activations held across ALL ``num_layers``.
481
+ Chunked or fused CE is assumed, so there is no ``[B, T, vocab]`` logits term (the thing that made
482
+ GC-off impossible at a 248k vocab). Unknown architecture dims -> ``inf`` (caller keeps GC on).
472
483
 
473
484
  MoE: the activation backbone scales with the model's real ``hidden`` x ``num_layers`` (geometry),
474
485
  NOT params_b -- the ~3B-active expert FFN is already folded into ``_GC_OFF_ACT_K``. ``weights``
@@ -497,7 +508,7 @@ def sft_grad_checkpoint_can_disable(
497
508
  quant: str = "bf16",
498
509
  margin_gb: float = 18.0,
499
510
  ) -> bool:
500
- """True when a FUSED-CE LoRA SFT step fits a ``card_vram_gb`` card WITHOUT gradient checkpointing,
511
+ """True when a dense-logit-free LoRA SFT step fits a ``card_vram_gb`` card WITHOUT gradient checkpointing,
501
512
  so GC -- a ~+33% recompute tax on every step -- can be turned off for the speed win.
502
513
 
503
514
  Conservative by construction: an unknown card / unknown architecture dims, or a peak that doesn't
@@ -716,12 +727,10 @@ def model_required_vram_gb(
716
727
  vllm_concurrency = (
717
728
  opd_rollout_concurrency(batch_size, group_size) if is_opd else group_size
718
729
  )
719
- # Worker parity: TRL SFT deliberately calls install_chalk_kernels(..., fused_ce=False), because
720
- # SFTTrainer.compute_loss reads outputs.logits and crashes when fused CE returns logits=None. So
721
- # every SFT run materializes dense logits, even for long context / >=3B models where the old
722
- # allocator assumed a fused CE path. Size SFT with fused CE OFF up front or long-context Qwen SFT
723
- # routes to consumer cards and OOMs on the first backward.
724
- sft_fused_ce = None if is_grpo else False
730
+ # trl chunked_nll removes ignored positions before the lm head and projects valid tokens in
731
+ # bounded chunks. size validated qwen sft jobs without a dense [batch, seq, vocab] term; models
732
+ # outside that validated set keep the conservative plain-nll estimate and micro-batch cap.
733
+ sft_fused_ce = None if is_grpo else sft_chunked_nll_enabled(model_id)
725
734
  if info is not None:
726
735
  params_b = info.params_b
727
736
  if model_revision:
@@ -109,11 +109,56 @@ def finalize_alloc_conf_for_sleep() -> None:
109
109
  print("[alloc] auto-conf skipped:", e)
110
110
 
111
111
 
112
+ def _is_cudagraph_capture_failure(exc: BaseException) -> bool:
113
+ markers = (
114
+ "capture_model",
115
+ "cuda graph",
116
+ "cudagraph",
117
+ "graph capture",
118
+ )
119
+ current: BaseException | None = exc
120
+ seen: set[int] = set()
121
+ while current is not None and id(current) not in seen:
122
+ seen.add(id(current))
123
+ if any(marker in f"{type(current).__name__}: {current}".lower() for marker in markers):
124
+ return True
125
+ traceback = current.__traceback__
126
+ while traceback is not None:
127
+ frame = traceback.tb_frame
128
+ location = f"{frame.f_code.co_filename}:{frame.f_code.co_name}".lower()
129
+ if any(marker in location for marker in markers):
130
+ return True
131
+ traceback = traceback.tb_next
132
+ current = current.__cause__ or current.__context__
133
+ return False
134
+
135
+
136
+ def _release_failed_cudagraph_capture(exc: BaseException) -> None:
137
+ current: BaseException | None = exc
138
+ seen: set[int] = set()
139
+ while current is not None and id(current) not in seen:
140
+ seen.add(id(current))
141
+ next_exc = current.__cause__ or current.__context__
142
+ current.__traceback__ = None
143
+ current = next_exc
144
+ try:
145
+ import gc
146
+
147
+ gc.collect()
148
+ import torch
149
+
150
+ if torch.cuda.is_available():
151
+ torch.cuda.empty_cache()
152
+ except Exception as cleanup_exc:
153
+ print(f"[rl][warn] cuda graph fallback cleanup failed: {cleanup_exc}")
154
+
155
+
112
156
  def patch_trl_colocate_llm_kwargs(
113
157
  *,
114
158
  kv_cache_dtype: str | None = None,
115
159
  max_num_batched_tokens: int | None = None,
116
160
  enforce_eager: bool | None = None,
161
+ compilation_config: dict | None = None,
117
162
  attention_backend: str | None = None,
118
163
  mm_encoder_attn_backend: str | None = None,
119
164
  reasoning_parser: str | None = None,
@@ -144,6 +189,8 @@ def patch_trl_colocate_llm_kwargs(
144
189
  * ``enforce_eager=True``: run the rollout engine in pure eager mode (no torch.compile, no CUDA-graph
145
190
  capture) — the SUPPORTED replacement for the removed ``VLLM_TORCH_COMPILE_LEVEL=0`` env. Dodges
146
191
  the vLLM 0.19.1 aot_compile (Ampere sm86) + Triton slot-mapping (graph-capture) crashes off B200.
192
+ * ``compilation_config``: select a narrower vLLM compilation profile, such as decode-only CUDA
193
+ graphs with torch.compile/AOT disabled.
147
194
  * ``attention_backend="FLASHINFER"|"TRITON_ATTN"``: pin a PTX-independent decoder attention backend
148
195
  (consumer-Blackwell sm120) — the SUPPORTED replacement for the removed ``VLLM_ATTENTION_BACKEND``
149
196
  env. ``EngineArgs`` coerces the bare member name through ``AttentionConfig.validate_backend_before``.
@@ -171,6 +218,8 @@ def patch_trl_colocate_llm_kwargs(
171
218
  new_overrides["max_num_batched_tokens"] = int(max_num_batched_tokens)
172
219
  if enforce_eager is not None:
173
220
  new_overrides["enforce_eager"] = bool(enforce_eager)
221
+ if compilation_config is not None:
222
+ new_overrides["compilation_config"] = dict(compilation_config)
174
223
  if attention_backend is not None:
175
224
  new_overrides["attention_backend"] = attention_backend
176
225
  if mm_encoder_attn_backend is not None:
@@ -194,7 +243,9 @@ def patch_trl_colocate_llm_kwargs(
194
243
  _vg._flash_llm_overrides = overrides
195
244
  overrides.update(new_overrides)
196
245
  if getattr(_vg, "_flash_llm_kwargs_patched", False):
197
- print(f"[rl] colocate vLLM LLM kwargs extended (wrapper already installed): {new_overrides}")
246
+ print(
247
+ f"[rl] colocate vLLM LLM kwargs extended (wrapper already installed): {new_overrides}"
248
+ )
198
249
  return True
199
250
  # TRL binds the module-global ``LLM`` only under ``if is_vllm_available():`` (it's the symbol the
200
251
  # colocate ``self.llm = LLM(...)`` references). If vLLM isn't importable here there is nothing to
@@ -205,11 +256,31 @@ def patch_trl_colocate_llm_kwargs(
205
256
  return False
206
257
 
207
258
  def _patched_LLM(*args, **kwargs):
208
- # Override TRL's hardcoded/absent values with ours (the kwargs TRL/env can't set). Read the
259
+ # override trl's hardcoded/absent values with ours (the kwargs trl/env can't set). read the
209
260
  # accumulated dict live so kwargs registered by later calls are applied too.
210
261
  kwargs.update(_vg._flash_llm_overrides)
211
262
  print(f"[rl] colocate vLLM LLM(...) kwargs override applied: {_vg._flash_llm_overrides}")
212
- return _orig_LLM(*args, **kwargs)
263
+ compilation = kwargs.get("compilation_config") or {}
264
+ decode_graphs = (
265
+ not kwargs.get("enforce_eager", False)
266
+ and compilation.get("cudagraph_mode") == "FULL_DECODE_ONLY"
267
+ )
268
+ try:
269
+ return _orig_LLM(*args, **kwargs)
270
+ except Exception as capture_exc:
271
+ if not decode_graphs or not _is_cudagraph_capture_failure(capture_exc):
272
+ raise
273
+ _release_failed_cudagraph_capture(capture_exc)
274
+ eager_kwargs = dict(kwargs)
275
+ eager_kwargs["enforce_eager"] = True
276
+ eager_kwargs.pop("compilation_config", None)
277
+ _vg._flash_llm_overrides["enforce_eager"] = True
278
+ _vg._flash_llm_overrides.pop("compilation_config", None)
279
+ print(
280
+ f"[rl][warn] decode-only cuda graph capture failed ({capture_exc}); "
281
+ "retrying colocate vLLM initialization once in eager mode"
282
+ )
283
+ return _orig_LLM(*args, **eager_kwargs)
213
284
 
214
285
  _vg.LLM = _patched_LLM
215
286
  _vg._flash_llm_kwargs_patched = True
@@ -18,6 +18,7 @@ import time
18
18
 
19
19
  from flash.engine.worker._pkg import W as _w
20
20
  from flash.engine.worker.perf import gpu_diagnostics
21
+ from flash.engine.worker.rollout_samples import sanitize_rollout_text
21
22
 
22
23
  # Setup-phase liveness stages: emitted from a 30s liveness thread WITH a progress callback during the
23
24
  # cold download / model-load / split-scan phase, kept on the tighter setup-liveness upload cadence
@@ -151,26 +152,20 @@ def heartbeat(stage: str, *, liveness: bool = False, force: bool = False, **kw):
151
152
  if stage in _HB_TIGHT_LIVENESS_STAGES:
152
153
  interval_s = min(interval_s, _w._HB_SETUP_LIVENESS_INTERVAL_S)
153
154
  upload_due = not throttled or (now - _w._HB_LAST_UPLOAD) >= interval_s
154
- # ``force`` bypasses the per-stage throttle (but not TERMINAL_ONLY mode, handled above) when
155
- # THIS heartbeat's payload must be the one on record and a throttled drop would leave a STALE
156
- # value committed -- opd's post-optimizer-step ping forces so a mid-step progress ping
157
- # (carrying the previous opt_steps) that just claimed the slot can't suppress the stepped
158
- # commit (else a cancel is billed from the stale step). Force is gated on STEP ADVANCE (this
159
- # payload's ``step`` exceeds the last committed step) AND a per-force time floor measured from
160
- # the last FORCED commit. The floor throttles only a SUB-FLOOR BURST of step advances (a
161
- # tiny/fast OPD config landing many optimizer updates per minute, which unthrottled would blow
162
- # the HF per-repo commit cap before the final adapter/DONE upload, codex[bot]); because the
163
- # floor keys off the last forced commit -- not the last upload of any kind -- a force still
164
- # punches through IMMEDIATELY after a liveness/mid-step ping stole the slot, and steps spaced
165
- # farther apart than the floor (the normal teacher-round-trip-gated regime) each still commit
166
- # exactly once, so a cancel there still bills the true latest step. The bounded cost is that a
167
- # cancel DURING a sub-floor burst under-bills by up to one floor-window of steps -- acceptable
168
- # (it favours the customer) and unavoidable once steps outrun the commit cap.
155
+ # ``force`` bypasses the per-stage throttle when this payload must be on record. it normally
156
+ # requires a step advance, but a sample-bearing payload may match the committed step because
157
+ # the liveness daemon can commit that step first without the samples. the per-force floor still
158
+ # coalesces fast bursts to protect the hf commit cap, while an unrelated liveness commit does
159
+ # not arm the floor and therefore cannot suppress the first sample-bearing payload.
169
160
  if force and not upload_due:
170
161
  fstep = kw.get("step")
162
+ has_samples = bool(kw.get("sampled_completions"))
163
+ force_step_due = isinstance(fstep, (int, float)) and (
164
+ fstep > _w._HB_LAST_COMMITTED_STEP
165
+ or (has_samples and fstep == _w._HB_LAST_COMMITTED_STEP)
166
+ )
171
167
  if (
172
- isinstance(fstep, (int, float))
173
- and fstep > _w._HB_LAST_COMMITTED_STEP
168
+ force_step_due
174
169
  and (now - _w._HB_LAST_FORCED_UPLOAD) >= _w._HB_FORCE_MIN_INTERVAL_S
175
170
  ):
176
171
  upload_due = True
@@ -193,6 +188,7 @@ def heartbeat(stage: str, *, liveness: bool = False, force: bool = False, **kw):
193
188
  _committed_step = kw.get("step")
194
189
  if isinstance(_committed_step, (int, float)) and _committed_step > _w._HB_LAST_COMMITTED_STEP:
195
190
  _w._HB_LAST_COMMITTED_STEP = int(_committed_step)
191
+ payload_committed = False
196
192
  if upload_due:
197
193
  critical = _is_critical_stage(stage)
198
194
  lock_timeout = _HB_CRITICAL_UPLOAD_LOCK_TIMEOUT_S if critical else _HB_UPLOAD_LOCK_TIMEOUT_S
@@ -210,19 +206,27 @@ def heartbeat(stage: str, *, liveness: bool = False, force: bool = False, **kw):
210
206
  # ``is False`` (not falsy) so a mock/None never trips the rollback.
211
207
  _rollback_throttle_slot(my_claim, prev_last_upload, prev_last_step, prev_last_forced)
212
208
  print(f"HEARTBEAT upload failed; rolled back throttle slot for {stage}")
213
- elif not liveness:
214
- # this committed snapshot carried real progress; settle the progress-carry
215
- # latch up to the seq captured when the snapshot was built (max: a concurrent
216
- # newer real heartbeat that lost the upload race must stay pending).
217
- with _HB_LOCK:
218
- if my_progress_seq > _w._HB_PROGRESS_UPLOADED_SEQ:
219
- _w._HB_PROGRESS_UPLOADED_SEQ = my_progress_seq
209
+ else:
210
+ payload_committed = True
211
+ if not liveness:
212
+ # this committed snapshot carried real progress; settle the progress-carry
213
+ # latch up to the seq captured when the snapshot was built (max: a concurrent
214
+ # newer real heartbeat that lost the upload race must stay pending).
215
+ with _HB_LOCK:
216
+ if my_progress_seq > _w._HB_PROGRESS_UPLOADED_SEQ:
217
+ _w._HB_PROGRESS_UPLOADED_SEQ = my_progress_seq
220
218
  finally:
221
219
  _HB_UPLOAD_LOCK.release()
222
220
  else:
223
221
  _rollback_throttle_slot(my_claim, prev_last_upload, prev_last_step, prev_last_forced)
224
222
  print(f"HEARTBEAT upload-lock busy >{lock_timeout}s; skipping commit for {stage}")
225
- print("HEARTBEAT", snapshot)
223
+ console_payload = payload
224
+ if not payload_committed and payload.get("sampled_completions"):
225
+ console_payload = {
226
+ key: value for key, value in payload.items() if key != "sampled_completions"
227
+ }
228
+ print("HEARTBEAT", json.dumps(console_payload))
229
+ return payload_committed
226
230
 
227
231
 
228
232
  def _maybe_attach_gpu_diag(payload: dict, last_gpu_diag_at: float, now: float) -> float:
@@ -290,7 +294,58 @@ def _bounded_reward_metrics(metrics) -> dict[str, float]:
290
294
  return {n: surviving[n] for n in sorted(priority + rest)}
291
295
 
292
296
 
293
- def make_reward_heartbeat_callback(reward_metrics=None):
297
+ # Exactly three samples per heartbeat, always. Mirrors rollout_samples._SAMPLE_LIMIT.
298
+ _SAMPLE_LIMIT = 3
299
+
300
+
301
+ def _sampled_completion_scalar(sample: dict) -> tuple[str, float] | None:
302
+ """Return the (key, finite value) of a sample's scalar: GRPO ``reward`` or OPD ``loss``."""
303
+ for key in ("reward", "loss"):
304
+ if key not in sample:
305
+ continue
306
+ try:
307
+ value = float(sample.get(key))
308
+ except (TypeError, ValueError):
309
+ return None
310
+ return (key, value) if math.isfinite(value) else None
311
+ return None
312
+
313
+
314
+ def _bounded_sampled_completions(samples) -> list[dict]:
315
+ if not isinstance(samples, (list, tuple)):
316
+ return []
317
+ bounded: list[dict] = []
318
+ for sample in samples:
319
+ if not isinstance(sample, dict):
320
+ continue
321
+ prompt_tail = sample.get("prompt_tail")
322
+ completion = sample.get("completion")
323
+ if not isinstance(prompt_tail, str) or not isinstance(completion, str):
324
+ continue
325
+ scalar = _sampled_completion_scalar(sample)
326
+ if scalar is None:
327
+ continue
328
+ generated_at_step = sample.get("generated_at_step")
329
+ if generated_at_step is not None:
330
+ try:
331
+ generated_at_step = int(generated_at_step)
332
+ except (TypeError, ValueError):
333
+ continue
334
+ scalar_key, scalar_value = scalar
335
+ bounded.append(
336
+ {
337
+ "prompt_tail": sanitize_rollout_text(prompt_tail),
338
+ "completion": sanitize_rollout_text(completion),
339
+ scalar_key: scalar_value,
340
+ "generated_at_step": generated_at_step,
341
+ }
342
+ )
343
+ if len(bounded) >= _SAMPLE_LIMIT:
344
+ break
345
+ return bounded
346
+
347
+
348
+ def make_reward_heartbeat_callback(reward_metrics=None, samples=None):
294
349
  """Return a TRL callback that streams per-step reward to the HF heartbeat channel."""
295
350
  from transformers import TrainerCallback
296
351
 
@@ -298,6 +353,7 @@ def make_reward_heartbeat_callback(reward_metrics=None):
298
353
  def __init__(self):
299
354
  self.reward_history = []
300
355
  self.last_gpu_diag_at = 0.0
356
+ self.sent_first_sample_heartbeat = False
301
357
  self.latest_reward_metrics: dict[str, float] = {}
302
358
 
303
359
  def latest_fields(self) -> dict:
@@ -326,9 +382,19 @@ def make_reward_heartbeat_callback(reward_metrics=None):
326
382
  self.latest_reward_metrics = _bounded_reward_metrics(latest_metrics)
327
383
  if self.latest_reward_metrics:
328
384
  payload["reward_metrics"] = dict(self.latest_reward_metrics)
385
+ latest_samples = samples() if callable(samples) else samples
386
+ bounded_samples = _bounded_sampled_completions(latest_samples)
387
+ if bounded_samples:
388
+ payload["sampled_completions"] = bounded_samples
329
389
  now = time.monotonic()
330
390
  self.last_gpu_diag_at = _maybe_attach_gpu_diag(payload, self.last_gpu_diag_at, now)
331
- _w.heartbeat("rl_step", **payload)
391
+ force_first_samples = bool(bounded_samples) and not self.sent_first_sample_heartbeat
392
+ if force_first_samples:
393
+ committed = _w.heartbeat("rl_step", force=True, **payload)
394
+ if committed:
395
+ self.sent_first_sample_heartbeat = True
396
+ else:
397
+ _w.heartbeat("rl_step", **payload)
332
398
 
333
399
  return _RewardHeartbeat()
334
400