freesolo-flash-dev 0.2.40__tar.gz → 0.2.41__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.40 → freesolo_flash_dev-0.2.41}/PKG-INFO +1 -1
  2. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/catalog.py +38 -2
  3. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/cli/__init__.py +17 -2
  4. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/cli/commands.py +168 -5
  5. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/cli/render.py +2 -2
  6. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/cli/training_doc.py +16 -15
  7. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/engine/chalk_kernels.py +10 -2
  8. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/engine/worker/__init__.py +2 -0
  9. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/engine/worker/perf/__init__.py +2 -0
  10. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/engine/worker/perf/memory.py +44 -0
  11. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/engine/worker/rl.py +5 -1
  12. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/engine/worker/sft.py +25 -44
  13. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/envs/adapter.py +8 -2
  14. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/lora_rank.py +42 -7
  15. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/providers/_worker.py +1 -1
  16. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/runner/__init__.py +6 -4
  17. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/schema/__init__.py +1 -1
  18. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/spec.py +1 -1
  19. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/pyproject.toml +2 -2
  20. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_catalog_consistency.py +3 -1
  21. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_chalk_kernels.py +42 -1
  22. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_cli_commands.py +1 -0
  23. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_flash_worker.py +10 -7
  24. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_sft_gc_off.py +44 -6
  25. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_spec_and_validation.py +7 -4
  26. freesolo_flash_dev-0.2.41/tests/test_train_context_preflight.py +82 -0
  27. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_verifiers.py +11 -8
  28. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/.dockerignore +0 -0
  29. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/.env.example +0 -0
  30. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/.github/workflows/auto-rebake.yml +0 -0
  31. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/.github/workflows/bake-kernel-cache.yml +0 -0
  32. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/.github/workflows/ci.yml +0 -0
  33. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/.github/workflows/main-source-guard.yml +0 -0
  34. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/.github/workflows/notify-tests-repo.yml +0 -0
  35. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/.github/workflows/publish-dev.yml +0 -0
  36. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/.github/workflows/publish-image.yml +0 -0
  37. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/.github/workflows/publish.yml +0 -0
  38. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/.github/workflows/version-parity.yml +0 -0
  39. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/.github/workflows/worker-image.yml +0 -0
  40. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/.gitignore +0 -0
  41. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/Dockerfile +0 -0
  42. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/Dockerfile.worker +0 -0
  43. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/LICENSE +0 -0
  44. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/README.md +0 -0
  45. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/build/kernel_cache/.gitignore +0 -0
  46. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/build/kernel_cache/.keep +0 -0
  47. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/docker/Dockerfile.kernelcache +0 -0
  48. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/docker/Dockerfile.kernelcache.relayer +0 -0
  49. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/docker/bake_kernel_cache.py +0 -0
  50. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/docker/bake_pod_entry.py +0 -0
  51. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/docker/kernel_fingerprint.py +0 -0
  52. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/docker/make_rp_handler.py +0 -0
  53. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/docs/cli-style/README.md +0 -0
  54. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/docs/cli-style/generate.py +0 -0
  55. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/docs/cli-style/index.html +0 -0
  56. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/docs/cli-style/preview.png +0 -0
  57. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/docs/cli-style/themed-errors.png +0 -0
  58. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/docs/cli-style/train-config-error-after.png +0 -0
  59. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/docs/cli-style/train-config-error-before.png +0 -0
  60. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/docs/kernel-cache.md +0 -0
  61. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/__init__.py +0 -0
  62. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/_channel.py +0 -0
  63. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/_fileio.py +0 -0
  64. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/_logging.py +0 -0
  65. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/_update_check.py +0 -0
  66. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/adapter_artifacts.py +0 -0
  67. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/cli/__main__.py +0 -0
  68. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/cli/_tty.py +0 -0
  69. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/cli/envpush.py +0 -0
  70. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/client/__init__.py +0 -0
  71. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/client/config.py +0 -0
  72. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/client/http.py +0 -0
  73. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/client/runtime_secrets.py +0 -0
  74. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/client/specs.py +0 -0
  75. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/cost/__init__.py +0 -0
  76. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/cost/analytical.py +0 -0
  77. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/cost/facts.py +0 -0
  78. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/cost/spec.py +0 -0
  79. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/cost/types.py +0 -0
  80. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/engine/__init__.py +0 -0
  81. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/engine/accounting.py +0 -0
  82. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/engine/multiturn_rollout.py +0 -0
  83. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/engine/recipe.py +0 -0
  84. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/engine/vram.py +0 -0
  85. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/engine/worker/__main__.py +0 -0
  86. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/engine/worker/_pkg.py +0 -0
  87. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/engine/worker/adapter.py +0 -0
  88. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/engine/worker/decoding.py +0 -0
  89. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/engine/worker/finalize.py +0 -0
  90. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/engine/worker/gpu_setup.py +0 -0
  91. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/engine/worker/grpo.py +0 -0
  92. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/engine/worker/heartbeat.py +0 -0
  93. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/engine/worker/hf.py +0 -0
  94. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/engine/worker/kernel_warmup.py +0 -0
  95. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/engine/worker/lora.py +0 -0
  96. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/engine/worker/packing.py +0 -0
  97. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/engine/worker/perf/attn.py +0 -0
  98. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/engine/worker/perf/diagnostics.py +0 -0
  99. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/engine/worker/perf/lifecycle.py +0 -0
  100. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/engine/worker/perf/liger.py +0 -0
  101. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/engine/worker/perf/loraplus.py +0 -0
  102. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/engine/worker/wandb_log.py +0 -0
  103. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/envs/__init__.py +0 -0
  104. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/envs/archive_policy.py +0 -0
  105. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/envs/base.py +0 -0
  106. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/envs/loader.py +0 -0
  107. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/envs/pull.py +0 -0
  108. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/envs/registry.py +0 -0
  109. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/providers/__init__.py +0 -0
  110. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/providers/_auth.py +0 -0
  111. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/providers/_hf_artifacts.py +0 -0
  112. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/providers/_hf_retry.py +0 -0
  113. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/providers/_http.py +0 -0
  114. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/providers/_instance.py +0 -0
  115. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/providers/_instance_bootstrap.py +0 -0
  116. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/providers/_instance_poll.py +0 -0
  117. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/providers/_instance_provider.py +0 -0
  118. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/providers/_poll.py +0 -0
  119. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/providers/allocator.py +0 -0
  120. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/providers/base.py +0 -0
  121. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/providers/lambdalabs/__init__.py +0 -0
  122. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/providers/lambdalabs/api.py +0 -0
  123. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/providers/lambdalabs/auth.py +0 -0
  124. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/providers/lambdalabs/gpus.py +0 -0
  125. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/providers/lambdalabs/jobs/__init__.py +0 -0
  126. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/providers/lambdalabs/jobs/builders.py +0 -0
  127. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/providers/lambdalabs/preflight.py +0 -0
  128. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/providers/lambdalabs/pricing.py +0 -0
  129. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/providers/preflight.py +0 -0
  130. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/providers/realized.py +0 -0
  131. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/providers/runpod/__init__.py +0 -0
  132. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/providers/runpod/api.py +0 -0
  133. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/providers/runpod/auth.py +0 -0
  134. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/providers/runpod/cost.py +0 -0
  135. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/providers/runpod/gpus.py +0 -0
  136. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/providers/runpod/jobs.py +0 -0
  137. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/providers/runpod/keys.py +0 -0
  138. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/providers/runpod/preflight.py +0 -0
  139. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/providers/runpod/preload.py +0 -0
  140. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/providers/runpod/pricing.py +0 -0
  141. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/providers/runpod/slots.py +0 -0
  142. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/providers/runpod/train/__init__.py +0 -0
  143. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/providers/runpod/train/deps.py +0 -0
  144. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/providers/runpod/train/endpoints.py +0 -0
  145. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/providers/vast/__init__.py +0 -0
  146. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/providers/vast/api.py +0 -0
  147. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/providers/vast/auth.py +0 -0
  148. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/providers/vast/jobs/__init__.py +0 -0
  149. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/providers/vast/jobs/builders.py +0 -0
  150. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/providers/vast/preflight.py +0 -0
  151. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/providers/vast/pricing.py +0 -0
  152. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/py.typed +0 -0
  153. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/runner/checkpoints.py +0 -0
  154. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/runner/deploy.py +0 -0
  155. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/runner/lifecycle.py +0 -0
  156. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/schema/fields.py +0 -0
  157. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/serve/__init__.py +0 -0
  158. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/serve/deploy.py +0 -0
  159. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/serve/export.py +0 -0
  160. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/serve/pricing.py +0 -0
  161. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/server/__init__.py +0 -0
  162. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/server/__main__.py +0 -0
  163. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/server/_deps.py +0 -0
  164. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/server/_internal_client.py +0 -0
  165. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/server/_locks.py +0 -0
  166. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/server/_runtime.py +0 -0
  167. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/server/app.py +0 -0
  168. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/server/auth.py +0 -0
  169. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/server/billing.py +0 -0
  170. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/server/billing_retry.py +0 -0
  171. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/server/checkpoints.py +0 -0
  172. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/server/db.py +0 -0
  173. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/server/environment_registry.py +0 -0
  174. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/server/envs.py +0 -0
  175. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/server/reconcile.py +0 -0
  176. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/server/routes/__init__.py +0 -0
  177. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/server/routes/envs.py +0 -0
  178. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/server/routes/meta.py +0 -0
  179. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/server/routes/runs.py +0 -0
  180. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/server/routes/serving.py +0 -0
  181. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/flash/server/run_registry.py +0 -0
  182. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/infisical-entrypoint.sh +0 -0
  183. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/scripts/build_dev_dist.py +0 -0
  184. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/__init__.py +0 -0
  185. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/_helpers/__init__.py +0 -0
  186. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/_helpers/runner.py +0 -0
  187. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/_helpers/specs.py +0 -0
  188. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/_helpers/vast.py +0 -0
  189. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/conftest.py +0 -0
  190. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/fixtures/math_eval.jsonl +0 -0
  191. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/fixtures/math_train.jsonl +0 -0
  192. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/live/__init__.py +0 -0
  193. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/live/conftest.py +0 -0
  194. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/live/test_lambda_live.py +0 -0
  195. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/live/test_runpod_live.py +0 -0
  196. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/live/test_vast_live.py +0 -0
  197. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_agent_flash_cli_contract.py +0 -0
  198. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_algorithms.py +0 -0
  199. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_allocator.py +0 -0
  200. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_b200_rollout_opt.py +0 -0
  201. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_backend_jobspec_contract.py +0 -0
  202. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_billing_retry.py +0 -0
  203. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_cancel_remote.py +0 -0
  204. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_charge_pricing.py +0 -0
  205. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_checkpoints.py +0 -0
  206. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_cli_errors.py +0 -0
  207. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_cli_estimate.py +0 -0
  208. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_cli_help.py +0 -0
  209. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_cli_managed.py +0 -0
  210. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_cli_render_theme.py +0 -0
  211. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_client.py +0 -0
  212. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_client_server_integration.py +0 -0
  213. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_config_overrides.py +0 -0
  214. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_cost_analytical.py +0 -0
  215. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_cost_equation.py +0 -0
  216. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_cost_estimate.py +0 -0
  217. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_cost_hardware.py +0 -0
  218. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_cost_models.py +0 -0
  219. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_cost_rewards.py +0 -0
  220. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_dev_channel.py +0 -0
  221. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_disk_gb.py +0 -0
  222. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_endpoint_name.py +0 -0
  223. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_env_cache_evict.py +0 -0
  224. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_env_delete.py +0 -0
  225. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_env_download.py +0 -0
  226. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_env_download_api.py +0 -0
  227. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_env_no_install.py +0 -0
  228. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_env_publish.py +0 -0
  229. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_env_pull.py +0 -0
  230. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_env_pull_managed_control_plane.py +0 -0
  231. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_env_push.py +0 -0
  232. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_env_rate_limit_resolve.py +0 -0
  233. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_envs_coverage.py +0 -0
  234. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_export.py +0 -0
  235. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_flash_mvp.py +0 -0
  236. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_flashinfer_cache_dirs.py +0 -0
  237. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_github_urlopen_retry.py +0 -0
  238. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_gpus.py +0 -0
  239. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_grpo_mask_aware.py +0 -0
  240. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_grpo_params.py +0 -0
  241. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_grpo_sleep_gate.py +0 -0
  242. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_health_liveness.py +0 -0
  243. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_idle_endpoint_reaper.py +0 -0
  244. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_internal_client.py +0 -0
  245. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_jobs.py +0 -0
  246. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_kernel_cache.py +0 -0
  247. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_kernel_fingerprint.py +0 -0
  248. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_kv_util.py +0 -0
  249. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_lambda_runner.py +0 -0
  250. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_logging.py +0 -0
  251. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_login_perms.py +0 -0
  252. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_lora_rank_preflight.py +0 -0
  253. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_managed_hf_repo.py +0 -0
  254. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_metrics_schema_agent_contract.py +0 -0
  255. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_mig_guard.py +0 -0
  256. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_multiturn_rollout.py +0 -0
  257. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_oom_escalate_gpu.py +0 -0
  258. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_open_model_policy.py +0 -0
  259. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_orchestrator_flash.py +0 -0
  260. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_packing.py +0 -0
  261. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_poll_helpers.py +0 -0
  262. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_preflight.py +0 -0
  263. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_provider_routing.py +0 -0
  264. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_provider_teardown_robustness.py +0 -0
  265. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_providers_symmetry.py +0 -0
  266. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_realized_cost.py +0 -0
  267. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_resolve_params_b.py +0 -0
  268. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_resume_on_retry.py +0 -0
  269. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_runmgmt.py +0 -0
  270. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_runpod_api_delete.py +0 -0
  271. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_runpod_key_fingerprint.py +0 -0
  272. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_runpod_key_waterfall.py +0 -0
  273. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_runpod_slots.py +0 -0
  274. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_serve.py +0 -0
  275. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_server_api.py +0 -0
  276. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_server_billing.py +0 -0
  277. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_server_db.py +0 -0
  278. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_serving_contract.py +0 -0
  279. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_thinking_config.py +0 -0
  280. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_update_check.py +0 -0
  281. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_vast_api.py +0 -0
  282. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_vast_offers.py +0 -0
  283. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_vast_runner.py +0 -0
  284. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_version.py +0 -0
  285. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_vl_warmstart_adapter_keys.py +0 -0
  286. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_vl_warmstart_recombine.py +0 -0
  287. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_vl_weight_sync.py +0 -0
  288. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_wandb_naming.py +0 -0
  289. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_warmstart_cross_repo.py +0 -0
  290. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_weight_cache.py +0 -0
  291. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_worker_dryrun.py +0 -0
  292. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_worker_hardexit.py +0 -0
  293. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_worker_image.py +0 -0
  294. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_worker_init_heartbeat.py +0 -0
  295. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_worker_stack.py +0 -0
  296. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/tests/test_worker_thinking.py +0 -0
  297. {freesolo_flash_dev-0.2.40 → freesolo_flash_dev-0.2.41}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: freesolo-flash-dev
3
- Version: 0.2.40
3
+ Version: 0.2.41
4
4
  Summary: Flash — managed LoRA post-training (SFT/GRPO) 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
@@ -12,7 +12,7 @@ ALGORITHMS = ("sft", "grpo")
12
12
  def normalize_algorithm(value: str) -> str:
13
13
  """Canonical (lowercased, validated) algorithm name."""
14
14
  if not value:
15
- value = "grpo"
15
+ value = "sft"
16
16
  elif not isinstance(value, str):
17
17
  # A truthy non-string (e.g. a JSON number/bool/array) would AttributeError on .lower(), which
18
18
  # escapes the callers' ValueError/ConfigError guards -> uncaught 500. Raise ValueError instead.
@@ -95,6 +95,16 @@ class ModelInfo:
95
95
  num_layers: int = 0
96
96
  hidden_size: int = 0
97
97
 
98
+ @property
99
+ def is_moe(self) -> bool:
100
+ """True for a mixture-of-experts model — a token routes through only a subset of experts.
101
+
102
+ Keyed off ``active_params_b`` (0.0 == dense, "every token hits every param"). Used by the
103
+ GRPO worker to pick REENTRANT gradient checkpointing for MoE (its router re-dispatches tokens
104
+ on recompute, which the non-reentrant metadata-equality assert rejects).
105
+ """
106
+ return 0.0 < self.active_params_b < self.params_b
107
+
98
108
  def to_dict(self) -> dict[str, Any]:
99
109
  data = asdict(self)
100
110
  serving = data.get("serving")
@@ -263,9 +273,15 @@ MODELS: dict[str, ModelInfo] = {
263
273
  # rank-64 at only 6 hot slots: the fused-MoE LoRA buffer scales with
264
274
  # max_loras x rank x num_experts, so the A100-80GB ceiling is ~max_loras x rank = 384
265
275
  # (6 x 64 fits at 99.3% util; 16 x 64 OOMs on every single/multi GPU). Serving-validated.
276
+ # The 6 x 64 ceiling is WEIGHT-bound, not context-bound: the FP8 checkpoint's MoE experts
277
+ # load as bf16 under the LoRA path (~76 GiB on the 80 GiB card), so weights + the 6 x 64
278
+ # buffer leave only ~0.44 GiB for KV. Canary-measured 2026-07-04: at 8192 ctx that KV pool
279
+ # gives 2.2x concurrency; dropping to 4096 does NOT free any LoRA-slot room (7 x 64 and
280
+ # 8 x 64 both OOM at 4096 exactly as at 8192) but ~doubles concurrency to ~4.4x. So context
281
+ # is 4096 (concurrency win, matches the training-context cap) while slots stay 6 x 64.
266
282
  max_loras=6,
267
283
  max_lora_rank=64,
268
- max_model_len=8192,
284
+ max_model_len=4096,
269
285
  max_num_seqs=8,
270
286
  max_num_batched_tokens=4096,
271
287
  gpu_memory_utilization=0.98,
@@ -313,6 +329,26 @@ def serving_lora_rank_cap(model: str | ModelInfo | None) -> int | None:
313
329
  return int(info.serving.max_lora_rank)
314
330
 
315
331
 
332
+ def serving_context_cap(model: str | ModelInfo | None) -> int | None:
333
+ """Return the model's serving ``max_model_len`` (the context it is actually served at), or None
334
+ when Flash has no local serving entry (open-policy / uncataloged).
335
+
336
+ A LoRA trained at a longer context than it is served wastes compute and learns positions that are
337
+ never used at inference, so the control plane caps a run's training context to this (see
338
+ ``flash.lora_rank.preflight_train_context_within_serving``). Resolution mirrors
339
+ ``serving_lora_rank_cap``: unknown/open-policy models return None rather than a global fallback.
340
+ """
341
+ if isinstance(model, ModelInfo):
342
+ info = model
343
+ elif isinstance(model, str) and model.strip():
344
+ info = MODELS.get(model.strip())
345
+ else:
346
+ info = None
347
+ if info is None or info.serving is None:
348
+ return None
349
+ return int(info.serving.max_model_len)
350
+
351
+
316
352
  def vocab_size_for(model_id: str) -> int:
317
353
  """Curated vocab_size for a model, or the safe default for open-model-policy entries."""
318
354
  info = MODELS.get(model_id)
@@ -168,7 +168,7 @@ class _FlashParser(_ThemedParser):
168
168
  footers = [
169
169
  f"new here? run `{CLI_NAME} login`, then `{CLI_NAME} env setup`",
170
170
  f"train after publishing: `{CLI_NAME} env push --name my-env .`, "
171
- f"then `{CLI_NAME} train configs/rl.toml`",
171
+ f"then `{CLI_NAME} train configs/sft.toml`",
172
172
  f"any command in depth: `{CLI_NAME} <command> --help`",
173
173
  "docs: https://freesolo.co/docs",
174
174
  ]
@@ -234,7 +234,22 @@ def _build_parser() -> argparse.ArgumentParser:
234
234
  env = sub.add_parser("env", help="manage Freesolo environments")
235
235
  env_sub = env.add_subparsers(dest="env_cmd", required=True)
236
236
  setup = env_sub.add_parser("setup", help="create a starter Freesolo environment scaffold")
237
- setup.set_defaults(func=cmd_env_setup)
237
+ setup_mode = setup.add_mutually_exclusive_group()
238
+ setup_mode.add_argument(
239
+ "--single-turn",
240
+ dest="turn_mode",
241
+ action="store_const",
242
+ const="single",
243
+ help="scaffold a single-turn environment (prompt -> one response). This is the default.",
244
+ )
245
+ setup_mode.add_argument(
246
+ "--multi-turn",
247
+ dest="turn_mode",
248
+ action="store_const",
249
+ const="multi",
250
+ help="scaffold a multi-turn environment (bounded episode with step_episode / score_episode).",
251
+ )
252
+ setup.set_defaults(func=cmd_env_setup, turn_mode="single")
238
253
 
239
254
  env_list = env_sub.add_parser("list", help="list local environment sources")
240
255
  env_list.set_defaults(func=cmd_env_list)
@@ -193,15 +193,177 @@ _STARTER_DATASET_JSONL = """\
193
193
  """
194
194
 
195
195
 
196
+ _STARTER_ENV_MULTITURN_PY = '''\
197
+ """Starter Freesolo multi-turn environment.
198
+
199
+ A multi-turn environment runs a bounded episode: the model produces an assistant
200
+ action, `step_episode` advances the world (optionally appending an observation
201
+ message), and the loop repeats until `done` or `max_episode_turns`. The finished
202
+ transcript is graded by `score_episode`.
203
+
204
+ Edit dataset/train.jsonl and the episode logic, then upload with
205
+ `flash env push --name my-env .`.
206
+
207
+ A managed run should use the returned [environment] id from
208
+ `flash env push --name my-env .`.
209
+
210
+ This starter implements a tiny "guess the secret number" game so you can see the
211
+ episode hooks wired end-to-end. Replace it with your real task before a real run.
212
+
213
+ Both SFT and GRPO train off this file:
214
+ - GRPO (configs/rl.toml) rolls out full episodes and optimizes `score_episode`.
215
+ - SFT (configs/sft.toml) learns the gold trajectory. Provide it per row as
216
+ `output = {"messages": [...]}` (a full assistant/tool trajectory) or a scalar
217
+ `output` for a single gold assistant turn.
218
+ """
219
+
220
+ from __future__ import annotations
221
+
222
+ import json
223
+ from pathlib import Path
224
+
225
+ from freesolo.datasets.types import TaskExample
226
+ from freesolo.environments import (
227
+ EnvironmentEpisode,
228
+ EnvironmentMultiTurn,
229
+ EnvironmentStepResult,
230
+ RewardResult,
231
+ )
232
+
233
+
234
+ DEFAULT_DATASET_PATH = Path(__file__).parent / "dataset" / "train.jsonl"
235
+
236
+ # How many assistant guesses the model gets before the episode is forced terminal.
237
+ MAX_TURNS = 5
238
+
239
+
240
+ def load_jsonl(path: str | Path):
241
+ rows = []
242
+ with Path(path).open() as f:
243
+ for line in f:
244
+ line = line.strip()
245
+ if line:
246
+ rows.append(json.loads(line))
247
+ return rows
248
+
249
+
250
+ def _secret(example: TaskExample) -> int:
251
+ return int(str(example.output).strip())
252
+
253
+
254
+ class StarterMultiTurnEnv(EnvironmentMultiTurn):
255
+ dataset = load_jsonl(DEFAULT_DATASET_PATH)
256
+
257
+ def start_episode(self, example: TaskExample, prompt_text: str):
258
+ # The opening prompt shown to the model. `example.input` describes the range.
259
+ return [
260
+ {
261
+ "role": "user",
262
+ "content": (
263
+ f"{example.input}\\n"
264
+ f"Reply with a single integer per turn. I will say 'higher', "
265
+ f"'lower', or 'correct'. You have {MAX_TURNS} guesses."
266
+ ),
267
+ }
268
+ ]
269
+
270
+ def max_episode_turns(self, example: TaskExample) -> int:
271
+ return MAX_TURNS
272
+
273
+ def step_episode(
274
+ self,
275
+ example: TaskExample,
276
+ messages: list,
277
+ assistant_response: str,
278
+ ) -> EnvironmentStepResult:
279
+ # Advance the world after one assistant action. Return done=True to end the
280
+ # episode, or append an observation message and keep going.
281
+ try:
282
+ guess = int(assistant_response.strip().split()[0])
283
+ except (ValueError, IndexError):
284
+ return EnvironmentStepResult(
285
+ done=False,
286
+ messages=[{"role": "user", "content": "Please reply with a single integer."}],
287
+ )
288
+ secret = _secret(example)
289
+ if guess == secret:
290
+ return EnvironmentStepResult(done=True, final_response_text=str(guess))
291
+ hint = "higher" if guess < secret else "lower"
292
+ return EnvironmentStepResult(
293
+ done=False,
294
+ messages=[{"role": "user", "content": hint}],
295
+ )
296
+
297
+ def score_episode(
298
+ self,
299
+ example: TaskExample,
300
+ episode: EnvironmentEpisode,
301
+ ) -> RewardResult:
302
+ # Grade the finished transcript. Reward a correct final guess; give partial
303
+ # credit for getting close so GRPO has a usable gradient.
304
+ secret = _secret(example)
305
+ try:
306
+ final = int(str(episode.response_text).strip())
307
+ except ValueError:
308
+ return RewardResult(score=0.0, threshold=1.0)
309
+ if final == secret:
310
+ return RewardResult(score=1.0, threshold=1.0, success=True)
311
+ # Closeness in [0, 1): further guesses score lower.
312
+ closeness = max(0.0, 1.0 - abs(final - secret) / 100.0)
313
+ return RewardResult(score=closeness * 0.5, threshold=1.0, success=False)
314
+
315
+
316
+ def load_environment(dataset_path: str | None = None, **kwargs) -> StarterMultiTurnEnv:
317
+ env = StarterMultiTurnEnv()
318
+ if dataset_path:
319
+ env.dataset = load_jsonl(dataset_path)
320
+ return env
321
+ '''
322
+
323
+
324
+ # Multi-turn rows: `input` sets up the episode, `output` is the secret number.
325
+ # The scalar `output` also serves as the gold single-turn SFT target; swap in
326
+ # `{"messages": [...]}` to teach a full gold trajectory.
327
+ _STARTER_DATASET_MULTITURN_JSONL = """\
328
+ {"input":"I picked a secret whole number between 1 and 100.","output":"42"}
329
+ {"input":"I picked a secret whole number between 1 and 100.","output":"73"}
330
+ """
331
+
332
+
196
333
  def cmd_env_setup(args) -> int:
334
+ requested_multi = getattr(args, "turn_mode", "single") == "multi"
335
+ starter_env = Path("environment.py")
336
+ dataset = Path("dataset/train.jsonl")
337
+ # An existing environment.py is the authoritative signal for which turn mode this
338
+ # scaffold already uses (the dataset is plain JSONL with no reliable mode marker).
339
+ # Anchor to it so a re-run never leaves a single-turn env beside a multi-turn
340
+ # dataset (or vice versa); the flag only decides the mode when starting fresh.
341
+ existing_multi: bool | None = None
342
+ anchor = "environment.py"
343
+ if starter_env.exists():
344
+ existing_multi = "EnvironmentMultiTurn" in starter_env.read_text(encoding="utf-8")
345
+ elif dataset.exists():
346
+ # No env.py to anchor on, but the starter multi-turn dataset carries a
347
+ # distinctive prompt; use it so we don't drop a single-turn env beside it.
348
+ existing_multi = "secret whole number" in dataset.read_text(encoding="utf-8")
349
+ anchor = "dataset/train.jsonl"
350
+ if existing_multi is not None and existing_multi != requested_multi:
351
+ have = "multi-turn" if existing_multi else "single-turn"
352
+ want = "multi-turn" if requested_multi else "single-turn"
353
+ msg = (
354
+ f"existing {anchor} is {have}; keeping it and ignoring --{want}. "
355
+ f"Delete environment.py and dataset/train.jsonl first to re-scaffold as {want}."
356
+ )
357
+ print(render.warn(msg) if render.styled() else f"warning: {msg}", file=sys.stderr)
358
+ multi_turn = requested_multi if existing_multi is None else existing_multi
359
+ env_py = _STARTER_ENV_MULTITURN_PY if multi_turn else _STARTER_ENV_PY
360
+ dataset_jsonl = _STARTER_DATASET_MULTITURN_JSONL if multi_turn else _STARTER_DATASET_JSONL
197
361
  Path("configs").mkdir(exist_ok=True)
198
362
  Path("dataset").mkdir(exist_ok=True)
199
- dataset = Path("dataset/train.jsonl")
200
363
  if not dataset.exists():
201
- dataset.write_text(_STARTER_DATASET_JSONL)
202
- starter_env = Path("environment.py")
364
+ dataset.write_text(dataset_jsonl)
203
365
  if not starter_env.exists():
204
- starter_env.write_text(_STARTER_ENV_PY)
366
+ starter_env.write_text(env_py)
205
367
  env_comment = (
206
368
  "# Environment: upload this project folder with\n"
207
369
  "# `flash env push --name my-env .`, then paste the returned id below.\n"
@@ -231,6 +393,7 @@ def cmd_env_setup(args) -> int:
231
393
  f"{env_comment}"
232
394
  "[train]\n"
233
395
  "epochs = 1\n"
396
+ "max_examples = 2 # rows to train on; the starter dataset has 2 (raise as your dataset grows)\n"
234
397
  "lora_rank = 32\n"
235
398
  "# GPU and HF artifacts are managed automatically by the platform: the GPU is\n"
236
399
  "# the cheapest fitting managed class, and artifacts live in a private environment-scoped repo.\n"
@@ -242,8 +405,8 @@ def cmd_env_setup(args) -> int:
242
405
  scaffolded = [
243
406
  "environment.py",
244
407
  "dataset/train.jsonl",
245
- "configs/rl.toml",
246
408
  "configs/sft.toml",
409
+ "configs/rl.toml",
247
410
  "TRAINING.md",
248
411
  ]
249
412
  if render.styled():
@@ -391,7 +391,7 @@ def models_table(rows: list[dict]) -> str:
391
391
  """Supported base models — a clean themed list of ids (the CLI lists ids only)."""
392
392
  dot = _glyph("•", "-")
393
393
  ids = "\n".join(f" {_paint(dot, _FAINT)} {_paint(r['id'], _ACCENT2)}" for r in rows)
394
- foot = arrow("train one with: flash train configs/rl.toml")
394
+ foot = arrow("train one with: flash train configs/sft.toml")
395
395
  return _safe(f"{header('models', 'supported base models')}\n{ids}\n\n{foot}")
396
396
 
397
397
 
@@ -643,8 +643,8 @@ def env_setup(paths: list[str]) -> str:
643
643
  labels = {
644
644
  "environment.py": "env entrypoint — edit the reward + prompt",
645
645
  "dataset/train.jsonl": "starter training rows",
646
- "configs/rl.toml": "GRPO run config",
647
646
  "configs/sft.toml": "SFT run config",
647
+ "configs/rl.toml": "GRPO run config",
648
648
  "TRAINING.md": "how to train well — read this first",
649
649
  }
650
650
  keyw = max(len(p) for p in paths)
@@ -40,8 +40,8 @@ flash gpus # managed GPU classes with estimated $/hr
40
40
  ```text
41
41
  environment.py # the task: how to prompt the model and how to score it
42
42
  dataset/train.jsonl # training rows, one JSON object per line: {"input": ..., "output": ...}
43
- configs/rl.toml # a GRPO (RL) run config
44
43
  configs/sft.toml # an SFT run config
44
+ configs/rl.toml # a GRPO (RL) run config
45
45
  TRAINING.md # this file
46
46
  ```
47
47
 
@@ -92,7 +92,7 @@ edit to `environment.py` or `dataset/` so the managed run uses your change.
92
92
 
93
93
  ```toml
94
94
  model = "Qwen/Qwen3.5-4B" # see `flash models`
95
- algorithm = "grpo" # "grpo" (RL) or "sft"
95
+ algorithm = "sft" # "sft" (supervised) or "grpo" (RL)
96
96
  # thinking = true # opt-in reasoning mode, for models that support it
97
97
 
98
98
  [environment]
@@ -102,10 +102,11 @@ id = "your-org/my-env" # the id printed by `flash env push`
102
102
  # never stored in the spec
103
103
 
104
104
  [train]
105
- steps = 150 # GRPO is step-driven; SFT is epoch-driven (epochs = N)
105
+ epochs = 1 # SFT is epoch-driven; GRPO is step-driven (steps = N)
106
+ max_examples = 2 # rows to train on (the starter dataset has 2)
106
107
  lora_rank = 32
107
108
  lora_alpha = 64
108
- # All GRPO/SFT knobs live under [train]. Do not add [grpo] or [sft] tables.
109
+ # All SFT/GRPO knobs live under [train]. Do not add [sft] or [grpo] tables.
109
110
  ```
110
111
 
111
112
  GPU and HF artifacts are **fully managed** — do not pick `gpu.type` or set
@@ -117,10 +118,10 @@ extra.toml` (deep-merge) and `--set key=value` (e.g. `--set train.steps=300`).
117
118
  ### 4. Submit
118
119
 
119
120
  ```bash
120
- flash train configs/rl.toml --dry-run # validate the config locally — no GPU, no charge
121
- flash train configs/rl.toml --cost # pre-flight USD estimate, then exit
122
- flash train configs/rl.toml # submit and follow logs (Ctrl-C detaches)
123
- flash train configs/rl.toml --background # submit and return immediately
121
+ flash train configs/sft.toml --dry-run # validate the config locally — no GPU, no charge
122
+ flash train configs/sft.toml --cost # pre-flight USD estimate, then exit
123
+ flash train configs/sft.toml # submit and follow logs (Ctrl-C detaches)
124
+ flash train configs/sft.toml --background # submit and return immediately
124
125
  ```
125
126
 
126
127
  ### 5. Monitor
@@ -159,9 +160,9 @@ Work in tight, attributable iterations. Each one is a hypothesis:
159
160
  1. Reconstruct state — what's the best run so far, and what have you already tried?
160
161
  2. Form a hypothesis — pick ONE lever and say WHY it will move the metric.
161
162
  3. Change that ONE lever.
162
- 4. Validate locally — `flash train configs/rl.toml --dry-run` (catches config errors
163
+ 4. Validate locally — `flash train configs/sft.toml --dry-run` (catches config errors
163
164
  for free; a paid run on a broken config or an all-zero reward is wasted budget).
164
- 5. Submit — `flash train configs/rl.toml`.
165
+ 5. Submit — `flash train configs/sft.toml`.
165
166
  6. Judge — read the metric trend AND a sample of real rollouts (see below).
166
167
  7. Keep the best run; revert the change if it didn't beat the noise band. Repeat.
167
168
  ```
@@ -181,7 +182,7 @@ different value.
181
182
  A run is only evidence of improvement when **all** of these hold:
182
183
 
183
184
  - [ ] The run reached `done` (confirmed via `flash status <run-id>`), not merely submitted.
184
- - [ ] The reward trend rose (GRPO `reward_mean`) or the SFT loss fell — **beyond the noise band**, not within it.
185
+ - [ ] The SFT loss fell or the reward trend rose (GRPO `reward_mean`) — **beyond the noise band**, not within it.
185
186
  - [ ] You **probed the trained adapter on real inputs** (`flash deploy` + `flash chat`), including cases it should fail — not just the metrics.
186
187
  - [ ] The score is real behavior, not empty/truncated/templated outputs, skipped rows, leakage, a swallowed exception, or a format-only win.
187
188
  - [ ] If you track a clean success signal separately from the shaped reward (an explicit `RewardMetric`), *that* moved too.
@@ -219,7 +220,7 @@ spending another GPU run:
219
220
  ## Judge the run, don't just finish it
220
221
 
221
222
  - **Judge the trend, not a single number.** The proof of training is the curve:
222
- `reward_mean` rising over steps (GRPO) or loss falling (SFT). Record the base/early
223
+ loss falling (SFT) or `reward_mean` rising over steps (GRPO). Record the base/early
223
224
  value and the final value. A flat or noisy trend with no improvement is not success.
224
225
  - **Read the model's outputs, not just the metrics.** A rising reward can come from
225
226
  reward-hacking or a degenerate output the reward still credits — metrics alone never
@@ -480,9 +481,9 @@ flash env setup # scaffold environment.py, dataset/, confi
480
481
  flash env push --name my-env . # publish the environment; paste the returned id into [environment]
481
482
  flash env pull your-org/my-env # download a published environment into the current folder
482
483
  flash env delete your-org/my-env -y # delete a published environment
483
- flash train configs/rl.toml --dry-run # validate the config locally (no GPU, no charge)
484
- flash train configs/rl.toml --cost # pre-flight USD estimate, then exit
485
- flash train configs/rl.toml # submit and follow logs (Ctrl-C detaches; --background to skip following)
484
+ flash train configs/sft.toml --dry-run # validate the config locally (no GPU, no charge)
485
+ flash train configs/sft.toml --cost # pre-flight USD estimate, then exit
486
+ flash train configs/sft.toml # submit and follow logs (Ctrl-C detaches; --background to skip following)
486
487
  flash status <run-id> # state + accrued cost
487
488
  flash log <run-id> # reward/loss trend + worker console/error logs
488
489
  flash log <run-id> --follow # stream a live run to completion
@@ -22,7 +22,6 @@ _KERNELS: list[tuple[str, bool]] = [
22
22
  ("trainable_attn_epilogue", True),
23
23
  ("fused_embedding", True),
24
24
  ("gdn", True),
25
- ("fused_mlp", False), # off: eval-only bf16 MLP forward, not the training activation path
26
25
  ("attn_epilogue", False), # off (eval-only; needs q/k/v out of LoRA)
27
26
  ("fp8_frozen_base", False), # off by default: speed/memory tradeoff, enable only after run A/B
28
27
  ]
@@ -54,15 +53,24 @@ def chalk_fused_ce_available(model_id: str | None = None) -> bool:
54
53
  return callable(_apply)
55
54
 
56
55
 
57
- def install_chalk_kernels(model=None) -> dict:
56
+ def install_chalk_kernels(model=None, *, fused_ce: bool = True) -> dict:
58
57
  """Apply chalk standalone kernels to ``model``; call AFTER TRL builds the trainer.
59
58
 
59
+ ``fused_ce=False`` skips the fused-linear-CE kernel. flce computes the loss inside a chunked
60
+ LM-head+CE kernel and returns ``logits=None`` (the whole point — it never materialises the
61
+ ``[batch, seq, vocab]`` logits). trl's ``SFTTrainer.compute_loss`` reads ``outputs.logits`` and
62
+ only skips it under ``use_liger_kernel=True`` — which flash can't set, since that makes trl apply
63
+ Liger and clash with chalk. So the SFT (trl) path passes ``fused_ce=False`` and lets the model
64
+ materialise logits; the custom GRPO/opd loops read the fused loss directly and keep it on.
65
+
60
66
  Returns chalk's per-kernel report, or ``{}`` when freesolo-chalk isn't installed.
61
67
  """
62
68
  if model is None:
63
69
  return {}
64
70
 
65
71
  kwargs = dict(_KERNELS)
72
+ if not fused_ce:
73
+ kwargs["fused_linear_cross_entropy"] = False
66
74
  try:
67
75
  from chalk.transformers import apply_chalk_kernel_to_qwen35
68
76
  except ImportError:
@@ -114,6 +114,7 @@ from flash.engine.worker.perf import (
114
114
  gpu_diagnostics,
115
115
  grad_checkpointing_on,
116
116
  grpo_sleep_mode,
117
+ grpo_use_reentrant,
117
118
  is_cuda_oom,
118
119
  liger_on,
119
120
  loraplus_optimizer_cls,
@@ -377,6 +378,7 @@ __all__ = [
377
378
  "grpo_mask_truncated_completions",
378
379
  "grpo_overrides",
379
380
  "grpo_sleep_mode",
381
+ "grpo_use_reentrant",
380
382
  "heartbeat",
381
383
  "hf_api",
382
384
  "hf_prefix",
@@ -54,6 +54,7 @@ from flash.engine.worker.perf.memory import (
54
54
  fused_optim_name,
55
55
  grad_checkpointing_on,
56
56
  grpo_sleep_mode,
57
+ grpo_use_reentrant,
57
58
  )
58
59
 
59
60
 
@@ -350,6 +351,7 @@ __all__ = [
350
351
  "gpu_diagnostics",
351
352
  "grad_checkpointing_on",
352
353
  "grpo_sleep_mode",
354
+ "grpo_use_reentrant",
353
355
  "is_cuda_oom",
354
356
  "liger_on",
355
357
  "loraplus_optimizer_cls",
@@ -71,6 +71,50 @@ def grad_checkpointing_on(
71
71
  return True
72
72
 
73
73
 
74
+ def _is_gdn_hybrid_family(model_id: str) -> bool:
75
+ """Offline family check for a Qwen3.5/3.6 GatedDeltaNet hybrid (no network/config probe).
76
+
77
+ Every curated Qwen3.5/3.6 model is a GDN hybrid; the non-Qwen curated model (MiniCPM, plain
78
+ Llama) and uncataloged open models are not. Kept as a string check so ``grpo_use_reentrant``
79
+ stays pure and hermetic (callable at config-build time, unit-testable without HF access).
80
+ """
81
+ mid = (model_id or "").lower()
82
+ return any(token in mid for token in ("qwen3.5", "qwen3_5", "qwen3.6", "qwen3_6"))
83
+
84
+
85
+ def grpo_use_reentrant(model_id: str) -> bool:
86
+ """Whether GRPO gradient checkpointing must use REENTRANT recompute for this model.
87
+
88
+ MoE models AND GatedDeltaNet (GDN) hybrids need it. Non-reentrant checkpointing
89
+ (``use_reentrant=False``) asserts that every recomputed activation's metadata matches the
90
+ forward pass and dies on the FIRST backward — before a single optimizer step — with
91
+ ``torch.utils.checkpoint: Recomputed values ... different metadata`` whenever a decoder layer
92
+ contains a custom, data-dependent kernel whose saved-for-backward tensors the recompute lays out
93
+ differently:
94
+
95
+ - MoE (Qwen3.6-35B-A3B): the router re-dispatches tokens on recompute, so the grouped
96
+ expert-buffer shapes differ (forward expert-dispatch tokens 28192 vs recompute 3524 ==
97
+ group_size x). This is what #429 fixed.
98
+ - GDN hybrids (Qwen3.5/3.6 dense): FlashAttention-2 varlen-unpad on the full-attention layers,
99
+ the fused GatedDeltaNet chunk-scan on the linear-attention layers, and chalk's fused Triton
100
+ kernels each save shape-/data-dependent tensors that the non-reentrant metadata-equality check
101
+ can't positionally reconcile (live-confirmed on Qwen3.5-0.8B GRPO / RTX 4090: forward packed
102
+ varlen ``[1636, ...]`` vs recompute padded ``[1024, ...]``). Same failure mode as MoE.
103
+
104
+ Reentrant checkpointing re-runs the forward inside the same autograd context over the same
105
+ closed-over inputs (mask/position_ids threaded via the ``partial``; ``use_cache=False``) and does
106
+ NOT assert metadata equality, so it tolerates these recomputes and produces correct gradients.
107
+ Non-GDN dense models (MiniCPM / plain-attention) keep the faster, lower-overhead non-reentrant
108
+ path.
109
+ """
110
+ from flash.catalog import MODELS
111
+
112
+ info = MODELS.get(model_id)
113
+ if info is not None and info.is_moe:
114
+ return True
115
+ return _is_gdn_hybrid_family(model_id)
116
+
117
+
74
118
  def grpo_sleep_mode(
75
119
  model_id: str,
76
120
  *,
@@ -27,6 +27,7 @@ from flash.engine.worker.perf import (
27
27
  fused_optim_name,
28
28
  gpu_diagnostics,
29
29
  grad_checkpointing_on,
30
+ grpo_use_reentrant,
30
31
  optimal_attn_impl,
31
32
  setup_perf_backends,
32
33
  wait_for_gpu,
@@ -354,7 +355,10 @@ def run_rl():
354
355
  "run_name": _w.wandb_run_name(),
355
356
  "seed": _w.SEED,
356
357
  "gradient_checkpointing": grad_checkpointing_on(model_id, vllm_max_len),
357
- "gradient_checkpointing_kwargs": {"use_reentrant": False},
358
+ # MoE needs REENTRANT recompute: its router re-dispatches tokens on the backward recompute,
359
+ # so non-reentrant's metadata-equality assert fires on the first backward and kills the run
360
+ # (Qwen3.6-35B-A3B). Dense models keep the faster non-reentrant path. See grpo_use_reentrant.
361
+ "gradient_checkpointing_kwargs": {"use_reentrant": grpo_use_reentrant(model_id)},
358
362
  # Pin a stable GRPO recipe instead of TRL's defaults (which suppress the lift on short runs):
359
363
  # constant LR, group-mean-centered advantages (no std scaling), no length-norm; beta = KL coef.
360
364
  "lr_scheduler_type": "constant",