freesolo-flash-dev 0.2.48__tar.gz → 0.2.49__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 (297) hide show
  1. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/PKG-INFO +1 -1
  2. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/cli/commands.py +4 -2
  3. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/cli/training_doc.py +14 -11
  4. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/cost/spec.py +63 -13
  5. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/engine/recipe.py +2 -3
  6. freesolo_flash_dev-0.2.49/flash/engine/steps.py +18 -0
  7. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/engine/worker/opd.py +35 -7
  8. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/engine/worker/rl.py +18 -4
  9. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/providers/_worker.py +0 -4
  10. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/schema/__init__.py +2 -9
  11. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/spec.py +0 -2
  12. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/pyproject.toml +2 -2
  13. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_agent_flash_cli_contract.py +2 -1
  14. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_algorithms.py +4 -4
  15. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_allocator.py +10 -10
  16. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_backend_jobspec_contract.py +6 -4
  17. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_billing_retry.py +1 -1
  18. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_charge_pricing.py +1 -1
  19. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_checkpoints.py +2 -2
  20. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_cli_commands.py +4 -2
  21. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_cli_errors.py +5 -3
  22. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_cli_estimate.py +29 -13
  23. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_cli_managed.py +2 -1
  24. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_client_server_integration.py +1 -1
  25. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_config_overrides.py +9 -5
  26. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_env_publish.py +1 -1
  27. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_env_rate_limit_resolve.py +1 -1
  28. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_flash_mvp.py +4 -2
  29. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_flash_worker.py +8 -8
  30. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_gpus.py +3 -2
  31. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_grpo_params.py +18 -17
  32. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_jobs.py +19 -18
  33. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_lora_rank_preflight.py +2 -3
  34. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_opd.py +20 -18
  35. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_orchestrator_flash.py +1 -1
  36. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_runmgmt.py +2 -2
  37. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_server_api.py +13 -13
  38. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_server_billing.py +1 -1
  39. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_spec_and_validation.py +2 -3
  40. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_weight_cache.py +1 -1
  41. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_worker_dryrun.py +13 -0
  42. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/.dockerignore +0 -0
  43. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/.env.example +0 -0
  44. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/.github/workflows/auto-rebake.yml +0 -0
  45. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/.github/workflows/bake-kernel-cache.yml +0 -0
  46. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/.github/workflows/ci.yml +0 -0
  47. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/.github/workflows/main-source-guard.yml +0 -0
  48. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/.github/workflows/notify-tests-repo.yml +0 -0
  49. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/.github/workflows/publish-dev.yml +0 -0
  50. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/.github/workflows/publish-image.yml +0 -0
  51. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/.github/workflows/publish.yml +0 -0
  52. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/.github/workflows/version-parity.yml +0 -0
  53. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/.github/workflows/worker-image.yml +0 -0
  54. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/.gitignore +0 -0
  55. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/Dockerfile +0 -0
  56. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/Dockerfile.worker +0 -0
  57. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/LICENSE +0 -0
  58. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/README.md +0 -0
  59. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/build/kernel_cache/.gitignore +0 -0
  60. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/build/kernel_cache/.keep +0 -0
  61. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/docker/Dockerfile.kernelcache +0 -0
  62. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/docker/Dockerfile.kernelcache.relayer +0 -0
  63. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/docker/bake_kernel_cache.py +0 -0
  64. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/docker/bake_pod_entry.py +0 -0
  65. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/docker/kernel_fingerprint.py +0 -0
  66. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/docker/make_rp_handler.py +0 -0
  67. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/__init__.py +0 -0
  68. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/_channel.py +0 -0
  69. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/_fileio.py +0 -0
  70. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/_logging.py +0 -0
  71. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/_update_check.py +0 -0
  72. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/adapter_artifacts.py +0 -0
  73. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/catalog.py +0 -0
  74. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/cli/__init__.py +0 -0
  75. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/cli/__main__.py +0 -0
  76. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/cli/_tty.py +0 -0
  77. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/cli/envpush.py +0 -0
  78. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/cli/render.py +0 -0
  79. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/client/__init__.py +0 -0
  80. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/client/config.py +0 -0
  81. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/client/http.py +0 -0
  82. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/client/runtime_secrets.py +0 -0
  83. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/client/specs.py +0 -0
  84. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/cost/__init__.py +0 -0
  85. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/cost/analytical.py +0 -0
  86. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/cost/facts.py +0 -0
  87. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/cost/types.py +0 -0
  88. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/engine/__init__.py +0 -0
  89. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/engine/accounting.py +0 -0
  90. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/engine/chalk_kernels.py +0 -0
  91. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/engine/multiturn_rollout.py +0 -0
  92. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/engine/vram.py +0 -0
  93. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/engine/worker/__init__.py +0 -0
  94. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/engine/worker/__main__.py +0 -0
  95. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/engine/worker/_pkg.py +0 -0
  96. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/engine/worker/adapter.py +0 -0
  97. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/engine/worker/decoding.py +0 -0
  98. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/engine/worker/finalize.py +0 -0
  99. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/engine/worker/gpu_setup.py +0 -0
  100. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/engine/worker/grpo.py +0 -0
  101. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/engine/worker/heartbeat.py +0 -0
  102. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/engine/worker/hf.py +0 -0
  103. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/engine/worker/kernel_warmup.py +0 -0
  104. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/engine/worker/lora.py +0 -0
  105. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/engine/worker/opd_gkd.py +0 -0
  106. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/engine/worker/opd_vllm.py +0 -0
  107. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/engine/worker/packing.py +0 -0
  108. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/engine/worker/perf/__init__.py +0 -0
  109. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/engine/worker/perf/attn.py +0 -0
  110. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/engine/worker/perf/diagnostics.py +0 -0
  111. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/engine/worker/perf/lifecycle.py +0 -0
  112. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/engine/worker/perf/liger.py +0 -0
  113. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/engine/worker/perf/loraplus.py +0 -0
  114. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/engine/worker/perf/memory.py +0 -0
  115. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/engine/worker/sft.py +0 -0
  116. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/engine/worker/teacher.py +0 -0
  117. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/engine/worker/tokenizer_align.py +0 -0
  118. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/engine/worker/wandb_log.py +0 -0
  119. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/envs/__init__.py +0 -0
  120. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/envs/adapter.py +0 -0
  121. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/envs/archive_policy.py +0 -0
  122. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/envs/base.py +0 -0
  123. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/envs/loader.py +0 -0
  124. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/envs/pull.py +0 -0
  125. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/envs/registry.py +0 -0
  126. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/lora_rank.py +0 -0
  127. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/providers/__init__.py +0 -0
  128. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/providers/_auth.py +0 -0
  129. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/providers/_hf_artifacts.py +0 -0
  130. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/providers/_hf_retry.py +0 -0
  131. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/providers/_http.py +0 -0
  132. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/providers/_instance.py +0 -0
  133. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/providers/_instance_bootstrap.py +0 -0
  134. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/providers/_instance_poll.py +0 -0
  135. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/providers/_instance_provider.py +0 -0
  136. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/providers/_poll.py +0 -0
  137. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/providers/allocator.py +0 -0
  138. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/providers/base.py +0 -0
  139. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/providers/lambdalabs/__init__.py +0 -0
  140. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/providers/lambdalabs/api.py +0 -0
  141. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/providers/lambdalabs/auth.py +0 -0
  142. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/providers/lambdalabs/gpus.py +0 -0
  143. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/providers/lambdalabs/jobs/__init__.py +0 -0
  144. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/providers/lambdalabs/jobs/builders.py +0 -0
  145. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/providers/lambdalabs/preflight.py +0 -0
  146. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/providers/lambdalabs/pricing.py +0 -0
  147. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/providers/preflight.py +0 -0
  148. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/providers/realized.py +0 -0
  149. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/providers/runpod/__init__.py +0 -0
  150. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/providers/runpod/api.py +0 -0
  151. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/providers/runpod/auth.py +0 -0
  152. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/providers/runpod/cost.py +0 -0
  153. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/providers/runpod/gpus.py +0 -0
  154. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/providers/runpod/jobs.py +0 -0
  155. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/providers/runpod/keys.py +0 -0
  156. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/providers/runpod/preflight.py +0 -0
  157. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/providers/runpod/preload.py +0 -0
  158. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/providers/runpod/pricing.py +0 -0
  159. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/providers/runpod/slots.py +0 -0
  160. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/providers/runpod/train/__init__.py +0 -0
  161. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/providers/runpod/train/deps.py +0 -0
  162. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/providers/runpod/train/endpoints.py +0 -0
  163. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/providers/vast/__init__.py +0 -0
  164. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/providers/vast/api.py +0 -0
  165. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/providers/vast/auth.py +0 -0
  166. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/providers/vast/jobs/__init__.py +0 -0
  167. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/providers/vast/jobs/builders.py +0 -0
  168. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/providers/vast/preflight.py +0 -0
  169. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/providers/vast/pricing.py +0 -0
  170. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/py.typed +0 -0
  171. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/runner/__init__.py +0 -0
  172. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/runner/checkpoints.py +0 -0
  173. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/runner/deploy.py +0 -0
  174. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/runner/lifecycle.py +0 -0
  175. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/schema/fields.py +0 -0
  176. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/serve/__init__.py +0 -0
  177. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/serve/deploy.py +0 -0
  178. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/serve/export.py +0 -0
  179. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/serve/pricing.py +0 -0
  180. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/server/__init__.py +0 -0
  181. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/server/__main__.py +0 -0
  182. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/server/_deps.py +0 -0
  183. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/server/_internal_client.py +0 -0
  184. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/server/_locks.py +0 -0
  185. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/server/_runtime.py +0 -0
  186. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/server/app.py +0 -0
  187. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/server/auth.py +0 -0
  188. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/server/billing.py +0 -0
  189. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/server/billing_retry.py +0 -0
  190. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/server/checkpoints.py +0 -0
  191. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/server/db.py +0 -0
  192. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/server/environment_registry.py +0 -0
  193. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/server/envs.py +0 -0
  194. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/server/reconcile.py +0 -0
  195. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/server/routes/__init__.py +0 -0
  196. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/server/routes/envs.py +0 -0
  197. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/server/routes/meta.py +0 -0
  198. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/server/routes/runs.py +0 -0
  199. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/server/routes/serving.py +0 -0
  200. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/flash/server/run_registry.py +0 -0
  201. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/infisical-entrypoint.sh +0 -0
  202. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/scripts/build_dev_dist.py +0 -0
  203. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/__init__.py +0 -0
  204. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/_helpers/__init__.py +0 -0
  205. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/_helpers/runner.py +0 -0
  206. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/_helpers/specs.py +0 -0
  207. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/_helpers/vast.py +0 -0
  208. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/conftest.py +0 -0
  209. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/fixtures/math_eval.jsonl +0 -0
  210. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/fixtures/math_train.jsonl +0 -0
  211. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/live/__init__.py +0 -0
  212. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/live/conftest.py +0 -0
  213. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/live/test_lambda_live.py +0 -0
  214. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/live/test_runpod_live.py +0 -0
  215. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/live/test_vast_live.py +0 -0
  216. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_b200_rollout_opt.py +0 -0
  217. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_cancel_remote.py +0 -0
  218. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_catalog_consistency.py +0 -0
  219. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_chalk_kernels.py +0 -0
  220. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_cli_help.py +0 -0
  221. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_cli_render_theme.py +0 -0
  222. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_client.py +0 -0
  223. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_cost_analytical.py +0 -0
  224. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_cost_equation.py +0 -0
  225. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_cost_estimate.py +0 -0
  226. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_cost_hardware.py +0 -0
  227. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_cost_models.py +0 -0
  228. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_cost_rewards.py +0 -0
  229. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_dev_channel.py +0 -0
  230. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_disk_gb.py +0 -0
  231. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_endpoint_name.py +0 -0
  232. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_env_cache_evict.py +0 -0
  233. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_env_delete.py +0 -0
  234. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_env_download.py +0 -0
  235. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_env_download_api.py +0 -0
  236. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_env_no_install.py +0 -0
  237. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_env_pull.py +0 -0
  238. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_env_pull_managed_control_plane.py +0 -0
  239. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_env_push.py +0 -0
  240. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_envs_coverage.py +0 -0
  241. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_export.py +0 -0
  242. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_flashinfer_cache_dirs.py +0 -0
  243. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_github_urlopen_retry.py +0 -0
  244. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_grpo_mask_aware.py +0 -0
  245. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_grpo_sleep_gate.py +0 -0
  246. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_health_liveness.py +0 -0
  247. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_idle_endpoint_reaper.py +0 -0
  248. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_internal_client.py +0 -0
  249. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_kernel_cache.py +0 -0
  250. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_kernel_fingerprint.py +0 -0
  251. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_kv_util.py +0 -0
  252. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_lambda_runner.py +0 -0
  253. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_logging.py +0 -0
  254. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_login_perms.py +0 -0
  255. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_managed_hf_repo.py +0 -0
  256. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_metrics_schema_agent_contract.py +0 -0
  257. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_mig_guard.py +0 -0
  258. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_multiturn_rollout.py +0 -0
  259. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_oom_escalate_gpu.py +0 -0
  260. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_opd_vllm.py +0 -0
  261. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_open_model_policy.py +0 -0
  262. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_packing.py +0 -0
  263. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_poll_helpers.py +0 -0
  264. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_preflight.py +0 -0
  265. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_provider_routing.py +0 -0
  266. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_provider_teardown_robustness.py +0 -0
  267. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_providers_symmetry.py +0 -0
  268. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_realized_cost.py +0 -0
  269. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_resolve_params_b.py +0 -0
  270. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_resume_on_retry.py +0 -0
  271. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_runpod_api_delete.py +0 -0
  272. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_runpod_key_fingerprint.py +0 -0
  273. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_runpod_key_waterfall.py +0 -0
  274. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_runpod_slots.py +0 -0
  275. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_serve.py +0 -0
  276. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_server_db.py +0 -0
  277. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_serving_contract.py +0 -0
  278. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_sft_example_selection.py +0 -0
  279. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_sft_gc_off.py +0 -0
  280. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_thinking_config.py +0 -0
  281. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_train_context_preflight.py +0 -0
  282. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_update_check.py +0 -0
  283. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_vast_api.py +0 -0
  284. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_vast_offers.py +0 -0
  285. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_vast_runner.py +0 -0
  286. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_verifiers.py +0 -0
  287. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_version.py +0 -0
  288. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_vl_warmstart_adapter_keys.py +0 -0
  289. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_vl_warmstart_recombine.py +0 -0
  290. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_wandb_naming.py +0 -0
  291. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_warmstart_cross_repo.py +0 -0
  292. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_worker_hardexit.py +0 -0
  293. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_worker_image.py +0 -0
  294. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_worker_init_heartbeat.py +0 -0
  295. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_worker_stack.py +0 -0
  296. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/tests/test_worker_thinking.py +0 -0
  297. {freesolo_flash_dev-0.2.48 → freesolo_flash_dev-0.2.49}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: freesolo-flash-dev
3
- Version: 0.2.48
3
+ Version: 0.2.49
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
@@ -469,7 +469,8 @@ def cmd_env_setup(args) -> int:
469
469
  "\n"
470
470
  f"{env_comment}"
471
471
  "[train]\n"
472
- "steps = 150\n"
472
+ "epochs = 1\n"
473
+ "max_examples = 2 # rows to train on; the starter dataset has 2 (raise as your dataset grows)\n"
473
474
  f"{rl_reasoning_train}"
474
475
  "lora_rank = 32\n"
475
476
  "# GPU and HF artifacts are managed automatically by the platform: the GPU is\n"
@@ -511,7 +512,8 @@ def cmd_env_setup(args) -> int:
511
512
  "[environment]\n"
512
513
  'id = ""\n\n'
513
514
  "[train]\n"
514
- "steps = 100 # opd is step-driven (like GRPO)\n"
515
+ "epochs = 1\n"
516
+ "max_examples = 2 # rows to train on; the starter dataset has 2 (raise as your dataset grows)\n"
515
517
  "lora_rank = 32\n"
516
518
  "# GPU and HF artifacts are managed automatically by the platform: the GPU is\n"
517
519
  "# the cheapest fitting managed class, and artifacts live in a private environment-scoped repo.\n"
@@ -103,7 +103,7 @@ id = "your-org/my-env" # the id printed by `flash env push`
103
103
  # never stored in the spec
104
104
 
105
105
  [train]
106
- epochs = 1 # SFT is epoch-driven; GRPO is step-driven (steps = N)
106
+ epochs = 1 # one pass over the retained train rows
107
107
  max_examples = 2 # rows to train on (the starter dataset has 2)
108
108
  lora_rank = 32
109
109
  lora_alpha = 64
@@ -114,7 +114,7 @@ GPU and HF artifacts are **fully managed** — do not pick `gpu.type` or set
114
114
  `train.hf_repo`; the allocator picks the cheapest validated managed GPU class that fits,
115
115
  and run artifacts are stored in a private environment-scoped repo with content-addressed
116
116
  Flash code snapshots. Compose or tweak configs without editing files: `--config
117
- extra.toml` (deep-merge) and `--set key=value` (e.g. `--set train.steps=300`).
117
+ extra.toml` (deep-merge) and `--set key=value` (e.g. `--set train.epochs=3`).
118
118
 
119
119
  ### 4. Submit
120
120
 
@@ -201,7 +201,7 @@ spending another GPU run:
201
201
  | --- | --- | --- |
202
202
  | Environment id is blank or stale | `flash train --dry-run` fails, or the worker uses old reward/data | Run `flash env push --name my-env .` after every environment/data edit and paste the returned id into every config you submit. |
203
203
  | Local-only env path in config | Config validation says there is no local path mode | Publish first, then use the returned slug in `[environment] id`. `flash train` only runs published env ids, not local paths. |
204
- | Config knobs are in the wrong table | Validation rejects `[grpo]`, `[sft]`, or unknown `[train]` keys | Put `steps`, `epochs`, `group_size`, `max_completion_tokens`, `temperature`, `max_context_tokens`, LoRA, and other training knobs under `[train]`. |
204
+ | Config knobs are in the wrong table | Validation rejects `[grpo]`, `[sft]`, or unknown `[train]` keys | Put `epochs`, `group_size`, `max_completion_tokens`, `temperature`, `max_context_tokens`, LoRA, and other training knobs under `[train]`. |
205
205
  | Trying to pin managed infrastructure | `gpu.type`, `train.hf_repo`, or `model_policy` changes do not do what you expected | Treat GPU choice, model policy, and the run artifact repo as managed. Tune the model, algorithm, environment, and `[train]` knobs instead. |
206
206
  | Secrets are not available on the worker | Reward code works locally but remote logs show missing API keys or auth failures | List secret names under `[environment] secrets = [...]`, export those env vars locally before submit, or put them in local `.env` / `.env.local`. Never put secret values in `[worker_env]` or hard-code them in the config. |
207
207
  | Wrong model / thinking setting | Config validation fails, or chat behavior does not match the run | Use `flash models`; set `thinking = true` only for supported models. Thinking is a training-time/run-level choice and serving preserves that parity, so `flash chat` does not expose an override flag. |
@@ -214,7 +214,7 @@ spending another GPU run:
214
214
  | Final checkpoint regresses | Last step is worse than an earlier checkpoint | Run `flash checkpoints <run-id>`, deploy a specific step with `flash deploy <run-id>/step-N`, and compare with held-out probes before exporting or relying on the final adapter. |
215
215
  | Export fails before upload | CLI says no HuggingFace token | Pass `flash export --api-key hf_...`, or set `HF_TOKEN` in your shell, `.env`, or `.env.local`. Exports are private unless you pass `--public`. |
216
216
  | SFT loss improves but quality does not | Train loss falls while held-out behavior stalls or degrades | Keep a held-out split outside training. Deploy and score that split; if quality drops, reduce epochs or improve data instead of adding more passes. |
217
- | Cost surprises | A quick experiment uses more GPU time than intended | Start with `--dry-run` and `--cost`, cap steps/epochs for smoke tests, and scale only after reward/data wiring is proven. Setup time is reported for observability; customer cost is based on training-loop GPU time. |
217
+ | Cost surprises | A quick experiment uses more GPU time than intended | Start with `--dry-run` and `--cost`, keep `epochs` and `max_examples` small for smoke tests, and scale only after reward/data wiring is proven. Setup time is reported for observability; customer cost is based on training-loop GPU time. |
218
218
 
219
219
  ---
220
220
 
@@ -370,7 +370,9 @@ lora_rank = 16 # for VL warm-starts, SFT rank + GRPO rank must fit the effec
370
370
  lora_alpha = 32
371
371
  ```
372
372
 
373
- SFT is **epoch-driven** (`epochs`); GRPO is **step-driven** (`steps`).
373
+ SFT, GRPO, and OPD all accept **epoch-driven** configs (`epochs`). For GRPO/OPD,
374
+ an epoch is one pass over the retained prompt pool after `max_examples` and prompt-budget filtering;
375
+ optimizer-step counts are derived from those epochs.
374
376
 
375
377
  ---
376
378
 
@@ -379,8 +381,8 @@ SFT is **epoch-driven** (`epochs`); GRPO is **step-driven** (`steps`).
379
381
  Pick distillation when a much stronger **teacher** model can grade your student's work
380
382
  token-by-token. The student samples on-policy (like GRPO), the managed GLM 5.2 teacher scores
381
383
  each of *its own* completions, and a dense per-token loss teaches the student to match the teacher —
382
- far more sample-efficient than reward-based RL and with no reward to design. It is **step-driven**
383
- (`steps`) and produces a LoRA served exactly like SFT.
384
+ far more sample-efficient than reward-based RL and with no reward to design. It supports `epochs`
385
+ like SFT/GRPO and produces a LoRA served exactly like SFT.
384
386
 
385
387
  - **No teacher key or model override to set up.** The GLM 5.2 teacher and its Fireworks key are platform-managed: the
386
388
  service supplies its own key to every opd run, so there is nothing to export or declare — an opd
@@ -412,7 +414,8 @@ algorithm = "opd"
412
414
  id = "your-org/my-env"
413
415
 
414
416
  [train]
415
- steps = 100
417
+ epochs = 1
418
+ max_examples = 2
416
419
  lora_rank = 32
417
420
  # kl_penalty_coef = 1.0 # reverse-KL scale
418
421
  ```
@@ -449,7 +452,7 @@ rollouts score somewhere between all-fail and all-pass — is where GRPO has the
449
452
  signal.
450
453
 
451
454
  - If nearly every prompt is solved (most groups score ~1.0): **increase difficulty** —
452
- harder prompts, tighter format/reward, more steps.
455
+ harder prompts, tighter format/reward, more epochs, or more data.
453
456
  - If nearly nothing is solved (most groups score ~0.0): **decrease difficulty** —
454
457
  easier or few-shot prompts, a more lenient (denser) reward, or warm-start with SFT.
455
458
  - In between: good signal — keep iterating at this difficulty.
@@ -500,8 +503,8 @@ on a beyond-noise improvement.
500
503
 
501
504
  ## Scale the evidence
502
505
 
503
- - **A smoke test is not proof.** A single-digit `steps`, a tiny dataset, or a handful
504
- of rollouts only validates the wiring. Scale `steps` / `epochs`, the dataset size,
506
+ - **A smoke test is not proof.** A single-digit derived step count, a tiny dataset, or a handful
507
+ of rollouts only validates the wiring. Scale `epochs`, the dataset size,
505
508
  and `group_size` to the model and the data you actually have before you trust a
506
509
  result. Don't cite budget alone as the reason for an underpowered run.
507
510
  - **Use the data you have.** Deliberately assign every usable row to training or to a
@@ -10,6 +10,7 @@ import math
10
10
  from flash.catalog import samples_on_policy
11
11
  from flash.cost.analytical import estimate_cost
12
12
  from flash.cost.types import CostEstimate, RunConfig
13
+ from flash.engine.steps import on_policy_steps
13
14
 
14
15
 
15
16
  def _sft_epochs(spec) -> int:
@@ -19,6 +20,14 @@ def _sft_epochs(spec) -> int:
19
20
  return int(t.epochs) if t.epochs is not None else RECIPE.sft.num_epochs
20
21
 
21
22
 
23
+ def _on_policy_epochs(spec) -> int:
24
+ from flash.engine.recipe import RECIPE
25
+
26
+ t = spec.train
27
+ default = RECIPE.rl.num_epochs if spec.algorithm == "grpo" else RECIPE.opd.num_epochs
28
+ return int(t.epochs) if t.epochs is not None else default
29
+
30
+
22
31
  def _sft_seq_len(spec) -> int:
23
32
  from flash.engine.recipe import RECIPE
24
33
 
@@ -41,6 +50,43 @@ def _sft_example_count(spec) -> int:
41
50
  )
42
51
 
43
52
 
53
+ def _on_policy_example_count(spec) -> int:
54
+ t = spec.train
55
+ pinned_examples = int(t.max_examples) if t.max_examples else 0
56
+ if pinned_examples > 0:
57
+ return pinned_examples
58
+ env_examples = _env_max_examples(spec)
59
+ if env_examples > 0:
60
+ return env_examples
61
+ return _on_policy_requested_prompts_per_step(spec)
62
+
63
+
64
+ def _env_max_examples(spec) -> int:
65
+ params = getattr(getattr(spec, "environment", None), "params", {}) or {}
66
+ if not isinstance(params, dict):
67
+ return 0
68
+ try:
69
+ value = int(params.get("max_examples") or 0)
70
+ except (TypeError, ValueError):
71
+ return 0
72
+ return max(0, value)
73
+
74
+
75
+ def _on_policy_requested_prompts_per_step(spec) -> int:
76
+ from flash.engine.recipe import RECIPE
77
+
78
+ t = spec.train
79
+ default = (
80
+ RECIPE.rl.prompts_per_step if spec.algorithm == "grpo" else RECIPE.opd.prompts_per_step
81
+ )
82
+ return max(1, int(t.batch_size) if t.batch_size is not None else default)
83
+
84
+
85
+ def _on_policy_prompts_per_step(spec, examples: int) -> int:
86
+ requested = _on_policy_requested_prompts_per_step(spec)
87
+ return max(1, min(requested, max(1, int(examples))))
88
+
89
+
44
90
  def _sft_realized_batch(spec) -> int:
45
91
  from flash.catalog import vocab_size_for
46
92
  from flash.engine.recipe import RECIPE
@@ -68,22 +114,26 @@ def _sft_steps_from_examples(spec, examples: int, *, apply_cap: bool) -> int:
68
114
 
69
115
 
70
116
  def spec_steps(spec) -> int:
71
- """Per-seed optimizer steps implied by a train spec (mirrors the worker). GRPO: ``train.steps``
72
- (else recipe default). SFT: ``epochs x ceil(num_examples / realized_batch)`` capped by
73
- ``max_steps``, where ``num_examples`` must be pinned by ``max_examples``."""
74
- from flash.engine.recipe import RECIPE
117
+ """Per-seed optimizer steps implied by a train spec (mirrors the worker).
75
118
 
76
- t = spec.train
119
+ SFT: ``epochs x ceil(num_examples / realized_batch)`` capped by ``max_steps``. GRPO/OPD:
120
+ ``epochs`` means passes over ``max_examples`` rows when pinned, otherwise the estimate uses one
121
+ prompt batch per epoch.
122
+ """
77
123
  if spec.algorithm == "grpo":
78
- if t.steps is not None:
79
- return max(1, int(t.steps))
80
- return RECIPE.rl.num_steps
124
+ examples = _on_policy_example_count(spec)
125
+ return on_policy_steps(
126
+ epochs=_on_policy_epochs(spec),
127
+ prompt_count=examples,
128
+ prompts_per_step=_on_policy_prompts_per_step(spec, examples),
129
+ )
81
130
  if spec.algorithm == "opd":
82
- # Step-driven like GRPO (on-policy sampling), NOT example-driven — so a opd quote never
83
- # demands [train].max_examples (the SFT fallback below would raise).
84
- if t.steps is not None:
85
- return max(1, int(t.steps))
86
- return RECIPE.opd.num_steps
131
+ examples = _on_policy_example_count(spec)
132
+ return on_policy_steps(
133
+ epochs=_on_policy_epochs(spec),
134
+ prompt_count=examples,
135
+ prompts_per_step=_on_policy_prompts_per_step(spec, examples),
136
+ )
87
137
  # max_examples is a CAP; 0 (like None) means "no cap" (worker trains the full dataset), so
88
138
  # don't let max_examples=0 price a single step.
89
139
  return _sft_steps_from_examples(spec, _sft_example_count(spec), apply_cap=True)
@@ -40,7 +40,7 @@ class RLConfig:
40
40
  max_completion_len_thinking: int = 1536
41
41
  prompts_per_step: int = 64
42
42
  group_size: int = 8
43
- num_steps: int = 150
43
+ num_epochs: int = 1
44
44
  sampling_temperature: float = 1.0
45
45
  sampling_top_p: float = 1.0
46
46
 
@@ -54,8 +54,6 @@ class OPDConfig:
54
54
  # platform-managed and intentionally not configurable from [train].
55
55
  teacher_model: str = "accounts/fireworks/models/glm-5p2"
56
56
  teacher_base_url: str = "https://api.fireworks.ai/inference/v1"
57
- # OPD is step-driven like GRPO (on-policy sampling), not epoch-driven like SFT.
58
- num_steps: int = 100
59
57
  learning_rate: float = 1e-5
60
58
  max_prompt_len: int = 1024
61
59
  max_completion_len: int = 512
@@ -65,6 +63,7 @@ class OPDConfig:
65
63
  # Student samples per prompt. 1 is enough for a direct KD loss (no group-relative baseline
66
64
  # as in GRPO); raise for more teacher-scored coverage per prompt at higher cost.
67
65
  group_size: int = 1
66
+ num_epochs: int = 1
68
67
  sampling_temperature: float = 1.0
69
68
  sampling_top_p: float = 1.0
70
69
  # Reverse-KL coefficient for the groupwise reverse-KL loss; scales the per-span
@@ -0,0 +1,18 @@
1
+ """Training-step derivation shared by worker and cost-estimate paths."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+
7
+
8
+ def on_policy_steps(
9
+ *,
10
+ epochs: int,
11
+ prompt_count: int,
12
+ prompts_per_step: int,
13
+ ) -> int:
14
+ """Resolve GRPO/OPD optimizer steps from full passes over the retained prompt pool."""
15
+ prompt_count = int(prompt_count)
16
+ if prompt_count <= 0:
17
+ raise ValueError("cannot derive epoch-based steps without at least one retained prompt")
18
+ return max(1, math.ceil(prompt_count * int(epochs) / max(1, int(prompts_per_step))))
@@ -29,6 +29,7 @@ from dataclasses import dataclass
29
29
 
30
30
  from flash.engine.chalk_kernels import active_kernels, install_chalk_kernels
31
31
  from flash.engine.recipe import RECIPE
32
+ from flash.engine.steps import on_policy_steps
32
33
  from flash.engine.vram import opd_completion_len
33
34
  from flash.engine.worker._pkg import W as _w
34
35
  from flash.engine.worker.heartbeat import liveness_heartbeat
@@ -75,7 +76,7 @@ class OpdKnobs:
75
76
 
76
77
  teacher_model: str = ""
77
78
  teacher_base_url: str = ""
78
- steps: int = 0
79
+ epochs: int = RECIPE.opd.num_epochs
79
80
  learning_rate: float = 0.0
80
81
  temperature: float = 0.0
81
82
  top_p: float = 1.0
@@ -116,7 +117,7 @@ def _resolve_opd_knobs() -> OpdKnobs:
116
117
  return OpdKnobs(
117
118
  teacher_model=d.teacher_model,
118
119
  teacher_base_url=d.teacher_base_url,
119
- steps=int(opt("steps", 0) or d.num_steps),
120
+ epochs=int(t.epochs) if t and t.epochs is not None else d.num_epochs,
120
121
  learning_rate=float(opt("learning_rate", 0) or d.learning_rate),
121
122
  temperature=float(
122
123
  opt("temperature", None)
@@ -263,7 +264,7 @@ def run_opd():
263
264
  warm_start = _w.JOB_SPEC.train.init_from_adapter if _w.JOB_SPEC else ""
264
265
  print(
265
266
  f"[opd] gkd (groupwise reverse-KL) teacher={knobs.teacher_model} "
266
- f"steps={knobs.steps} warm_start={warm_start or 'none'} "
267
+ f"epochs={knobs.epochs} warm_start={warm_start or 'none'} "
267
268
  f"mode={'multi-turn' if multi_turn else 'single-turn'}"
268
269
  )
269
270
 
@@ -309,9 +310,12 @@ def run_opd():
309
310
  "opd: the environment dataset is empty — no prompts to sample on-policy. Check the "
310
311
  "environment's dataset()/train split before provisioning a GPU."
311
312
  )
313
+ _max_examples = getattr(_w.JOB_SPEC.train, "max_examples", None) if _w.JOB_SPEC else None
314
+ max_examples = int(_max_examples or 0) if _max_examples is not None else 0
315
+ if max_examples > 0:
316
+ train = train[:max_examples]
312
317
  rng = random.Random(_w.SEED)
313
318
  rng.shuffle(train)
314
- steps = knobs.steps
315
319
  ppl_step = knobs.prompts_per_step
316
320
  group = knobs.group_size
317
321
  # Prompt budget mirrors GRPO: DROP (not truncate) prompts over the context budget, so the student
@@ -375,6 +379,21 @@ def run_opd():
375
379
  f"[opd] filtered {n_over_budget}/{len(train)} prompts over the "
376
380
  f"{prompt_budget}-token budget; pool = {len(examples)}"
377
381
  )
382
+ if ppl_step > len(examples):
383
+ print(
384
+ f"[opd] lowering prompts_per_step from {ppl_step} to {len(examples)}: "
385
+ "only that many prompt(s) fit after filtering"
386
+ )
387
+ ppl_step = len(examples)
388
+ steps = on_policy_steps(
389
+ epochs=knobs.epochs,
390
+ prompt_count=len(examples),
391
+ prompts_per_step=ppl_step,
392
+ )
393
+ print(
394
+ f"[opd] epochs={knobs.epochs} over {len(examples)} retained prompt(s) at "
395
+ f"{ppl_step} prompts/step -> steps={steps}"
396
+ )
378
397
 
379
398
  # Now that a non-empty on-policy pool is confirmed, prefetch the full base weights (deferred from
380
399
  # setup so an all-over-budget dataset fails before this download). Still inside the setup phase
@@ -573,9 +592,12 @@ def run_opd():
573
592
  # but opd only set attn_implementation at LOAD, so both the on-policy generate and the gkd loss
574
593
  # forward ran under the default SDPA dispatch and silently lost the cuDNN kernel (codex[bot]).
575
594
  # No-op (nullcontext) on non-Blackwell GPUs / when _attn isn't "sdpa".
595
+ def _samples_progress():
596
+ return samples_seen
597
+
576
598
  with (
577
599
  liveness_heartbeat(
578
- "opd_step", progress=lambda: samples_seen, fields=lambda: {"step": opt_steps}
600
+ "opd_step", progress=_samples_progress, fields=lambda: {"step": opt_steps}
579
601
  ),
580
602
  _sdpa_cudnn_ctx(_attn),
581
603
  ):
@@ -633,6 +655,7 @@ def run_opd():
633
655
  prompts.append(prompt_ids)
634
656
  with liveness_heartbeat(
635
657
  "opd_step",
658
+ progress=_samples_progress,
636
659
  fields=lambda _step=opt_steps: {"step": _step},
637
660
  keepalive=True,
638
661
  ):
@@ -804,6 +827,8 @@ def run_opd():
804
827
  generated_tokens=generated_tokens,
805
828
  notes={
806
829
  "steps": steps,
830
+ "epochs": knobs.epochs,
831
+ "retained_prompts": len(examples),
807
832
  # Optimizer steps actually applied; < steps if any iteration had no usable teacher
808
833
  # signal (skipped). loss_curve length == opt_steps, so reporting stays honest.
809
834
  "opt_steps": opt_steps,
@@ -976,8 +1001,11 @@ def _generate_one(*, model, tok, device, prompt_tensor, gen_cfg, knobs) -> _GenR
976
1001
  def _gen_from_vllm_output(out: OpdVllmOutput, tok, knobs) -> _GenResult:
977
1002
  """Apply OPD's pre-scoring gates to one vLLM completion."""
978
1003
  completion_ids = [int(t) for t in out.token_ids]
979
- completion_text = out.text or tok.decode(completion_ids, skip_special_tokens=True)
980
- stop_text = tok.decode(completion_ids, skip_special_tokens=False)
1004
+ decode = getattr(tok, "decode", None)
1005
+ completion_text = out.text or (
1006
+ decode(completion_ids, skip_special_tokens=True) if decode else ""
1007
+ )
1008
+ stop_text = decode(completion_ids, skip_special_tokens=False) if decode else completion_text
981
1009
  if not (
982
1010
  out.terminated
983
1011
  or _rollout_terminated(
@@ -8,6 +8,7 @@ import time
8
8
 
9
9
  from flash.engine.chalk_kernels import active_kernels, install_chalk_kernels
10
10
  from flash.engine.recipe import RECIPE
11
+ from flash.engine.steps import on_policy_steps
11
12
  from flash.engine.worker._pkg import W as _w
12
13
  from flash.engine.worker.grpo import resolve_grpo_sleep_mode
13
14
  from flash.engine.worker.heartbeat import liveness_heartbeat
@@ -54,11 +55,8 @@ def run_rl():
54
55
  model_id = _w.JOB_SPEC.model if _w.JOB_SPEC else RECIPE.hf_model_id
55
56
  download_seconds = _w.prefetch_model(model_id)
56
57
  rl = RECIPE.rl
57
- steps = int(
58
- _w.JOB_SPEC.train.steps if _w.JOB_SPEC and _w.JOB_SPEC.train.steps is not None else rl.num_steps
59
- )
60
- gcfg = _w.grpo_overrides()
61
58
  _t = _w.JOB_SPEC.train if _w.JOB_SPEC else None
59
+ gcfg = _w.grpo_overrides()
62
60
  prompts_per_step = int(
63
61
  _t.batch_size if _t and _t.batch_size is not None else rl.prompts_per_step
64
62
  )
@@ -91,6 +89,10 @@ def run_rl():
91
89
  tok.pad_token = tok.eos_token
92
90
 
93
91
  train = env.dataset()
92
+ _max_examples = getattr(_t, "max_examples", None) if _t else None
93
+ max_examples = int(_max_examples or 0) if _max_examples is not None else 0
94
+ if max_examples > 0:
95
+ train = train[:max_examples]
94
96
  rng = random.Random(_w.SEED)
95
97
  rng.shuffle(train)
96
98
  if conversational:
@@ -320,6 +322,16 @@ def run_rl():
320
322
  print(
321
323
  "WARN: generation batch not divisible by group size; check prompts_per_step/group_size"
322
324
  )
325
+ epochs = int(_t.epochs) if _t and _t.epochs is not None else RECIPE.rl.num_epochs
326
+ steps = on_policy_steps(
327
+ epochs=epochs,
328
+ prompt_count=len(prompts),
329
+ prompts_per_step=batching["unique_prompts_per_step"],
330
+ )
331
+ print(
332
+ f"[rl] epochs={epochs} over {len(prompts)} retained prompt(s) at "
333
+ f"{batching['unique_prompts_per_step']} unique_prompts/step -> steps={steps}"
334
+ )
323
335
  print(
324
336
  f"[rl] GRPO batching: per_device={batching['per_device_train_batch_size']} "
325
337
  f"grad_accum={batching['gradient_accumulation_steps']} "
@@ -692,6 +704,8 @@ def run_rl():
692
704
  generated_tokens=gen_tokens,
693
705
  notes={
694
706
  "steps": steps,
707
+ "epochs": epochs,
708
+ "retained_prompts": len(prompts),
695
709
  "resumed": bool(resume_ckpt),
696
710
  "download_seconds": download_seconds,
697
711
  "hf_transfer": os.environ.get("HF_HUB_ENABLE_HF_TRANSFER", ""),
@@ -235,10 +235,6 @@ def build_worker_env(
235
235
  env["HF_REPO"] = spec.train.hf_repo
236
236
  if getattr(spec.gpu, "network_volume", None):
237
237
  env.update(weight_cache_env())
238
- if spec.train.steps is not None:
239
- env["RL_STEPS"] = str(spec.train.steps)
240
- if spec.train.epochs is not None:
241
- env["SFT_EPOCHS"] = str(spec.train.epochs)
242
238
  for k in (
243
239
  "SFT_PER_DEVICE_BS",
244
240
  "VLLM_USE_V1",
@@ -190,7 +190,6 @@ _TOP_LEVEL_KEYS = frozenset(
190
190
  )
191
191
  _TRAIN_KEYS = frozenset(
192
192
  {
193
- "steps",
194
193
  "epochs",
195
194
  "lora_rank",
196
195
  "lora_alpha",
@@ -333,7 +332,6 @@ def spec_from_dict(raw: dict[str, Any], run_id: str | None = None) -> JobSpec:
333
332
  secrets=environment_secrets,
334
333
  ),
335
334
  train=TrainSpec(
336
- steps=_train_int(train_raw, "steps", minimum=1),
337
335
  epochs=_train_int(train_raw, "epochs", minimum=1),
338
336
  lora_rank=lora_rank,
339
337
  lora_alpha=_train_int(train_raw, "lora_alpha", minimum=1) or 64,
@@ -379,9 +377,7 @@ def _validate_sft(spec: JobSpec) -> None:
379
377
 
380
378
 
381
379
  def _validate_grpo(spec: JobSpec) -> None:
382
- """GRPO contract: step-driven, so steps (when pinned) must be positive."""
383
- if spec.train.steps is not None and spec.train.steps <= 0:
384
- raise ConfigError("train.steps must be positive for GRPO")
380
+ """GRPO contract: epochs derive passes over retained prompts."""
385
381
  if spec.train.group_size is not None and spec.train.group_size < 2:
386
382
  raise ConfigError(
387
383
  "train.group_size must be >= 2 for GRPO (TRL needs at least two generations "
@@ -390,13 +386,10 @@ def _validate_grpo(spec: JobSpec) -> None:
390
386
 
391
387
 
392
388
  def _validate_opd(spec: JobSpec) -> None:
393
- """OPD contract: step-driven (positive steps) with a usable prompt budget.
389
+ """OPD contract: epochs derive passes over retained prompts.
394
390
 
395
391
  The teacher key (FIREWORKS_API_KEY) is a platform-owned credential the control plane injects into
396
392
  the worker env (build_worker_env), like HF_TOKEN — never a user-declared secret."""
397
- if spec.train.steps is not None and spec.train.steps <= 0:
398
- # OPD is step-driven (on-policy sampling), like GRPO — not epoch-driven.
399
- raise ConfigError("train.steps must be positive for opd")
400
393
  if spec.train.max_context_tokens:
401
394
  # Mirror run_opd's prompt-budget guard at PARSE time: a context budget that leaves no room
402
395
  # for any prompt after the completion budget is rejected here, BEFORE a paid worker is
@@ -101,7 +101,6 @@ FIXED_SEED = 42
101
101
 
102
102
  @dataclass(frozen=True)
103
103
  class TrainSpec:
104
- steps: int | None = None
105
104
  epochs: int | None = None
106
105
  lora_rank: int = 32
107
106
  lora_alpha: int = 64
@@ -191,7 +190,6 @@ class JobSpec:
191
190
  resolved_sha=str(env.get("resolved_sha") or ""),
192
191
  ),
193
192
  train=TrainSpec(
194
- steps=_opt_int(train.get("steps")),
195
193
  epochs=_opt_int(train.get("epochs")),
196
194
  lora_rank=int(train.get("lora_rank", 32)),
197
195
  lora_alpha=int(train.get("lora_alpha", 64)),
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "freesolo-flash-dev"
3
- version = "0.2.48"
3
+ version = "0.2.49"
4
4
  description = "Flash — managed LoRA post-training (SFT/GRPO/OPD) for Freesolo environments, driven by the `flash` CLI"
5
5
  readme = "README.md"
6
6
  # RunPod Flash supports Python 3.11-3.12 (not 3.13 yet).
@@ -106,7 +106,7 @@ artifacts = ["flash/py.typed"]
106
106
  # auto-published by .github/workflows/publish-dev.yml: a push to `dev` whose version isn't yet on
107
107
  # PyPI publishes it; pushes that don't change it find it already published and no-op. So cutting a
108
108
  # dev-channel release = bump this (and [project].version) and merge to `dev`.
109
- version = "0.2.48"
109
+ version = "0.2.49"
110
110
 
111
111
  [tool.pytest.ini_options]
112
112
  pythonpath = ["."]
@@ -99,7 +99,8 @@ def test_train_dry_run_emits_run_id_and_state(tmp_path: Path, capsys) -> None:
99
99
  'id = "owner/env"\n'
100
100
  "[train]\n"
101
101
  'hf_repo = "owner/runs"\n'
102
- "steps = 10\n"
102
+ "epochs = 1\n"
103
+ "max_examples = 10\n"
103
104
  "[gpu]\n"
104
105
  'type = "RTX 5090"\n'
105
106
  )
@@ -23,7 +23,7 @@ def test_opd_algorithm_accepted():
23
23
  "model": "Qwen/Qwen3.5-4B",
24
24
  "algorithm": "opd",
25
25
  "environment": {"id": "github:owner/repo@main:env/environment.py"},
26
- "train": {"steps": 10, "hf_repo": "owner/runs"},
26
+ "train": {"epochs": 1, "max_examples": 10, "hf_repo": "owner/runs"},
27
27
  },
28
28
  run_id="x",
29
29
  )
@@ -55,7 +55,7 @@ def test_opd_capability_gated_per_model():
55
55
  "model": "test/no-opd",
56
56
  "algorithm": "opd",
57
57
  "environment": {"id": "github:owner/repo@main:env/environment.py"},
58
- "train": {"steps": 1, "hf_repo": "owner/runs"},
58
+ "train": {"epochs": 1, "max_examples": 1, "hf_repo": "owner/runs"},
59
59
  },
60
60
  run_id="x",
61
61
  )
@@ -84,7 +84,7 @@ def test_grpo_capability_still_enforced():
84
84
  "model": "test/sft-only",
85
85
  "algorithm": "grpo",
86
86
  "environment": {"id": "github:owner/repo@main:env/environment.py"},
87
- "train": {"steps": 1, "hf_repo": "owner/runs"},
87
+ "train": {"epochs": 1, "max_examples": 1, "hf_repo": "owner/runs"},
88
88
  },
89
89
  run_id="x",
90
90
  )
@@ -110,7 +110,7 @@ def test_qwen35_9b_now_supports_grpo():
110
110
  "model": "Qwen/Qwen3.5-9B",
111
111
  "algorithm": "grpo",
112
112
  "environment": {"id": "github:owner/repo@main:env/environment.py"},
113
- "train": {"steps": 1, "hf_repo": "owner/runs"},
113
+ "train": {"epochs": 1, "max_examples": 1, "hf_repo": "owner/runs"},
114
114
  "gpu": {"type": "A100 PCIe"},
115
115
  },
116
116
  run_id="x",
@@ -394,7 +394,7 @@ def test_observed_qwen2_opd_vllm_case_routes_off_32gb_cards(monkeypatch):
394
394
  from flash.providers.base import get_gpu_info, provisional_gpu
395
395
 
396
396
  monkeypatch.setattr(allocator, "available_providers", lambda: ("runpod",))
397
- train = {"steps": 60, "max_completion_tokens": 128, "lora_rank": 32, "lora_alpha": 64}
397
+ train = {"epochs": 1, "max_completion_tokens": 128, "lora_rank": 32, "lora_alpha": 64}
398
398
 
399
399
  need = required_vram_gb("Qwen/Qwen3.5-2B", "opd", train=train)
400
400
  assert need > 40
@@ -433,7 +433,7 @@ def test_observed_qwen4b_opd_vllm_startup_case_routes_off_40gb_cards(monkeypatch
433
433
 
434
434
  monkeypatch.setattr(allocator, "available_providers", lambda: ("runpod",))
435
435
  train = {
436
- "steps": 1,
436
+ "epochs": 1,
437
437
  "max_context_tokens": 8192,
438
438
  "max_completion_tokens": 128,
439
439
  "lora_rank": 32,
@@ -488,14 +488,14 @@ def test_opd_catalog_model_config_gpu_matrix_routes_to_fitting_cards(monkeypatch
488
488
  configs = {
489
489
  # Matches the failed continuation shape from the OPD/vLLM RTX 5090 report.
490
490
  "observed_2b_128tok_r32": {
491
- "steps": 1,
491
+ "epochs": 1,
492
492
  "max_completion_tokens": 128,
493
493
  "lora_rank": 32,
494
494
  "lora_alpha": 64,
495
495
  },
496
- "recipe_default": {"steps": 1},
496
+ "recipe_default": {"epochs": 1},
497
497
  "opd_prompt_batch": {
498
- "steps": 1,
498
+ "epochs": 1,
499
499
  "batch_size": 8,
500
500
  "group_size": 1,
501
501
  "max_context_tokens": 1536,
@@ -503,7 +503,7 @@ def test_opd_catalog_model_config_gpu_matrix_routes_to_fitting_cards(monkeypatch
503
503
  "lora_rank": 16,
504
504
  },
505
505
  "longer_context": {
506
- "steps": 1,
506
+ "epochs": 1,
507
507
  "batch_size": 8,
508
508
  "group_size": 1,
509
509
  "max_context_tokens": 4096,
@@ -511,7 +511,7 @@ def test_opd_catalog_model_config_gpu_matrix_routes_to_fitting_cards(monkeypatch
511
511
  "lora_rank": 16,
512
512
  },
513
513
  "longer_completion": {
514
- "steps": 1,
514
+ "epochs": 1,
515
515
  "batch_size": 1,
516
516
  "group_size": 1,
517
517
  "max_context_tokens": 4096,
@@ -519,7 +519,7 @@ def test_opd_catalog_model_config_gpu_matrix_routes_to_fitting_cards(monkeypatch
519
519
  "lora_rank": 16,
520
520
  },
521
521
  "wide_rollout_batch": {
522
- "steps": 1,
522
+ "epochs": 1,
523
523
  "batch_size": 8,
524
524
  "group_size": 4,
525
525
  "max_context_tokens": 4096,
@@ -538,7 +538,7 @@ def test_opd_catalog_model_config_gpu_matrix_routes_to_fitting_cards(monkeypatch
538
538
  rc = RunConfig(
539
539
  model_id,
540
540
  "opd",
541
- int(train.get("steps", 1)),
541
+ int(train.get("epochs", 1)),
542
542
  seq_len=train.get("max_context_tokens"),
543
543
  completion_len=train.get("max_completion_tokens"),
544
544
  batch_size=train.get("batch_size"),
@@ -675,7 +675,7 @@ def test_catalog_model_algorithm_config_gpu_matrix_resolves_to_fitting_cards(mon
675
675
  for algo in ALGORITHMS:
676
676
  if algo not in info.algos:
677
677
  continue
678
- train = {"epochs": 1, "max_examples": 8} if algo == "sft" else {"steps": 1}
678
+ train = {"epochs": 1, "max_examples": 8}
679
679
  need = allocator.required_vram_gb(model_id, algo, train=train, thinking=False)
680
680
  expected_gpu = provisional_gpu(model_id, algo, train=train, thinking=False)
681
681