freesolo-flash-dev 0.2.50__tar.gz → 0.2.55__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.50 → freesolo_flash_dev-0.2.55}/PKG-INFO +1 -1
  2. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/cli/training_doc.py +11 -0
  3. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/engine/multiturn_rollout.py +3 -3
  4. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/engine/recipe.py +9 -0
  5. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/engine/vram.py +68 -8
  6. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/engine/worker/__init__.py +2 -2
  7. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/engine/worker/adapter.py +36 -3
  8. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/engine/worker/lora.py +13 -85
  9. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/engine/worker/opd.py +575 -200
  10. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/engine/worker/opd_gkd.py +13 -84
  11. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/engine/worker/opd_vllm.py +8 -4
  12. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/engine/worker/rl.py +15 -2
  13. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/engine/worker/sft.py +6 -1
  14. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/engine/worker/teacher.py +72 -34
  15. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/engine/worker/tokenizer_align.py +2 -2
  16. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/providers/_hf_artifacts.py +2 -2
  17. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/providers/_instance_bootstrap.py +3 -3
  18. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/providers/base.py +58 -1
  19. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/providers/lambdalabs/jobs/__init__.py +2 -2
  20. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/providers/runpod/jobs.py +6 -6
  21. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/providers/runpod/train/endpoints.py +1 -2
  22. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/providers/vast/jobs/__init__.py +2 -2
  23. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/schema/__init__.py +3 -0
  24. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/serve/deploy.py +65 -1
  25. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/spec.py +4 -0
  26. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/pyproject.toml +2 -2
  27. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_allocator.py +35 -18
  28. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_flash_worker.py +12 -1
  29. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_gpus.py +21 -0
  30. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_jobs.py +36 -3
  31. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_opd.py +645 -310
  32. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_opd_vllm.py +1 -0
  33. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_serve.py +90 -3
  34. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_spec_and_validation.py +11 -0
  35. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_vl_warmstart_adapter_keys.py +10 -30
  36. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_vl_warmstart_recombine.py +27 -59
  37. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_worker_stack.py +67 -5
  38. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/uv.lock +1 -1
  39. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/.dockerignore +0 -0
  40. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/.env.example +0 -0
  41. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/.github/workflows/auto-rebake.yml +0 -0
  42. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/.github/workflows/bake-kernel-cache.yml +0 -0
  43. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/.github/workflows/ci.yml +0 -0
  44. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/.github/workflows/main-source-guard.yml +0 -0
  45. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/.github/workflows/notify-tests-repo.yml +0 -0
  46. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/.github/workflows/publish-dev.yml +0 -0
  47. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/.github/workflows/publish-image.yml +0 -0
  48. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/.github/workflows/publish.yml +0 -0
  49. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/.github/workflows/version-parity.yml +0 -0
  50. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/.github/workflows/worker-image.yml +0 -0
  51. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/.gitignore +0 -0
  52. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/Dockerfile +0 -0
  53. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/Dockerfile.worker +0 -0
  54. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/LICENSE +0 -0
  55. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/README.md +0 -0
  56. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/build/kernel_cache/.gitignore +0 -0
  57. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/build/kernel_cache/.keep +0 -0
  58. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/docker/Dockerfile.kernelcache +0 -0
  59. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/docker/Dockerfile.kernelcache.relayer +0 -0
  60. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/docker/bake_kernel_cache.py +0 -0
  61. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/docker/bake_pod_entry.py +0 -0
  62. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/docker/kernel_fingerprint.py +0 -0
  63. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/docker/make_rp_handler.py +0 -0
  64. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/__init__.py +0 -0
  65. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/_channel.py +0 -0
  66. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/_fileio.py +0 -0
  67. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/_logging.py +0 -0
  68. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/_update_check.py +0 -0
  69. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/adapter_artifacts.py +0 -0
  70. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/catalog.py +0 -0
  71. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/cli/__init__.py +0 -0
  72. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/cli/__main__.py +0 -0
  73. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/cli/_tty.py +0 -0
  74. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/cli/commands.py +0 -0
  75. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/cli/envpush.py +0 -0
  76. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/cli/render.py +0 -0
  77. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/client/__init__.py +0 -0
  78. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/client/config.py +0 -0
  79. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/client/http.py +0 -0
  80. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/client/runtime_secrets.py +0 -0
  81. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/client/specs.py +0 -0
  82. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/cost/__init__.py +0 -0
  83. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/cost/analytical.py +0 -0
  84. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/cost/facts.py +0 -0
  85. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/cost/spec.py +0 -0
  86. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/cost/types.py +0 -0
  87. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/engine/__init__.py +0 -0
  88. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/engine/accounting.py +0 -0
  89. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/engine/chalk_kernels.py +0 -0
  90. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/engine/steps.py +0 -0
  91. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/engine/worker/__main__.py +0 -0
  92. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/engine/worker/_pkg.py +0 -0
  93. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/engine/worker/decoding.py +0 -0
  94. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/engine/worker/finalize.py +0 -0
  95. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/engine/worker/gpu_setup.py +0 -0
  96. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/engine/worker/grpo.py +0 -0
  97. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/engine/worker/heartbeat.py +0 -0
  98. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/engine/worker/hf.py +0 -0
  99. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/engine/worker/kernel_warmup.py +0 -0
  100. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/engine/worker/packing.py +0 -0
  101. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/engine/worker/perf/__init__.py +0 -0
  102. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/engine/worker/perf/attn.py +0 -0
  103. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/engine/worker/perf/diagnostics.py +0 -0
  104. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/engine/worker/perf/lifecycle.py +0 -0
  105. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/engine/worker/perf/liger.py +0 -0
  106. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/engine/worker/perf/loraplus.py +0 -0
  107. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/engine/worker/perf/memory.py +0 -0
  108. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/engine/worker/wandb_log.py +0 -0
  109. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/envs/__init__.py +0 -0
  110. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/envs/adapter.py +0 -0
  111. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/envs/archive_policy.py +0 -0
  112. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/envs/base.py +0 -0
  113. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/envs/loader.py +0 -0
  114. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/envs/pull.py +0 -0
  115. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/envs/registry.py +0 -0
  116. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/lora_rank.py +0 -0
  117. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/providers/__init__.py +0 -0
  118. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/providers/_auth.py +0 -0
  119. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/providers/_hf_retry.py +0 -0
  120. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/providers/_http.py +0 -0
  121. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/providers/_instance.py +0 -0
  122. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/providers/_instance_poll.py +0 -0
  123. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/providers/_instance_provider.py +0 -0
  124. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/providers/_poll.py +0 -0
  125. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/providers/_worker.py +0 -0
  126. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/providers/allocator.py +0 -0
  127. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/providers/lambdalabs/__init__.py +0 -0
  128. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/providers/lambdalabs/api.py +0 -0
  129. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/providers/lambdalabs/auth.py +0 -0
  130. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/providers/lambdalabs/gpus.py +0 -0
  131. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/providers/lambdalabs/jobs/builders.py +0 -0
  132. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/providers/lambdalabs/preflight.py +0 -0
  133. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/providers/lambdalabs/pricing.py +0 -0
  134. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/providers/preflight.py +0 -0
  135. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/providers/realized.py +0 -0
  136. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/providers/runpod/__init__.py +0 -0
  137. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/providers/runpod/api.py +0 -0
  138. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/providers/runpod/auth.py +0 -0
  139. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/providers/runpod/cost.py +0 -0
  140. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/providers/runpod/gpus.py +0 -0
  141. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/providers/runpod/keys.py +0 -0
  142. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/providers/runpod/preflight.py +0 -0
  143. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/providers/runpod/preload.py +0 -0
  144. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/providers/runpod/pricing.py +0 -0
  145. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/providers/runpod/slots.py +0 -0
  146. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/providers/runpod/train/__init__.py +0 -0
  147. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/providers/runpod/train/deps.py +0 -0
  148. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/providers/vast/__init__.py +0 -0
  149. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/providers/vast/api.py +0 -0
  150. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/providers/vast/auth.py +0 -0
  151. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/providers/vast/jobs/builders.py +0 -0
  152. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/providers/vast/preflight.py +0 -0
  153. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/providers/vast/pricing.py +0 -0
  154. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/py.typed +0 -0
  155. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/runner/__init__.py +0 -0
  156. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/runner/checkpoints.py +0 -0
  157. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/runner/deploy.py +0 -0
  158. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/runner/lifecycle.py +0 -0
  159. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/schema/fields.py +0 -0
  160. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/serve/__init__.py +0 -0
  161. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/serve/export.py +0 -0
  162. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/serve/pricing.py +0 -0
  163. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/server/__init__.py +0 -0
  164. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/server/__main__.py +0 -0
  165. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/server/_deps.py +0 -0
  166. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/server/_internal_client.py +0 -0
  167. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/server/_locks.py +0 -0
  168. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/server/_runtime.py +0 -0
  169. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/server/app.py +0 -0
  170. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/server/auth.py +0 -0
  171. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/server/billing.py +0 -0
  172. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/server/billing_retry.py +0 -0
  173. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/server/checkpoints.py +0 -0
  174. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/server/db.py +0 -0
  175. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/server/environment_registry.py +0 -0
  176. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/server/envs.py +0 -0
  177. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/server/reconcile.py +0 -0
  178. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/server/routes/__init__.py +0 -0
  179. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/server/routes/envs.py +0 -0
  180. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/server/routes/meta.py +0 -0
  181. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/server/routes/runs.py +0 -0
  182. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/server/routes/serving.py +0 -0
  183. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/flash/server/run_registry.py +0 -0
  184. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/infisical-entrypoint.sh +0 -0
  185. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/scripts/build_dev_dist.py +0 -0
  186. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/__init__.py +0 -0
  187. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/_helpers/__init__.py +0 -0
  188. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/_helpers/runner.py +0 -0
  189. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/_helpers/specs.py +0 -0
  190. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/_helpers/vast.py +0 -0
  191. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/conftest.py +0 -0
  192. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/fixtures/math_eval.jsonl +0 -0
  193. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/fixtures/math_train.jsonl +0 -0
  194. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/live/__init__.py +0 -0
  195. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/live/conftest.py +0 -0
  196. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/live/test_lambda_live.py +0 -0
  197. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/live/test_runpod_live.py +0 -0
  198. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/live/test_vast_live.py +0 -0
  199. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_agent_flash_cli_contract.py +0 -0
  200. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_algorithms.py +0 -0
  201. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_b200_rollout_opt.py +0 -0
  202. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_backend_jobspec_contract.py +0 -0
  203. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_billing_retry.py +0 -0
  204. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_cancel_remote.py +0 -0
  205. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_catalog_consistency.py +0 -0
  206. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_chalk_kernels.py +0 -0
  207. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_charge_pricing.py +0 -0
  208. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_checkpoints.py +0 -0
  209. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_cli_commands.py +0 -0
  210. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_cli_errors.py +0 -0
  211. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_cli_estimate.py +0 -0
  212. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_cli_help.py +0 -0
  213. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_cli_managed.py +0 -0
  214. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_cli_render_theme.py +0 -0
  215. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_client.py +0 -0
  216. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_client_server_integration.py +0 -0
  217. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_config_overrides.py +0 -0
  218. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_cost_analytical.py +0 -0
  219. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_cost_equation.py +0 -0
  220. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_cost_estimate.py +0 -0
  221. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_cost_hardware.py +0 -0
  222. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_cost_models.py +0 -0
  223. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_cost_rewards.py +0 -0
  224. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_dev_channel.py +0 -0
  225. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_disk_gb.py +0 -0
  226. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_endpoint_name.py +0 -0
  227. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_env_cache_evict.py +0 -0
  228. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_env_delete.py +0 -0
  229. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_env_download.py +0 -0
  230. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_env_download_api.py +0 -0
  231. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_env_no_install.py +0 -0
  232. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_env_publish.py +0 -0
  233. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_env_pull.py +0 -0
  234. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_env_pull_managed_control_plane.py +0 -0
  235. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_env_push.py +0 -0
  236. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_env_rate_limit_resolve.py +0 -0
  237. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_envs_coverage.py +0 -0
  238. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_export.py +0 -0
  239. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_flash_mvp.py +0 -0
  240. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_flashinfer_cache_dirs.py +0 -0
  241. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_github_urlopen_retry.py +0 -0
  242. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_grpo_mask_aware.py +0 -0
  243. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_grpo_params.py +0 -0
  244. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_grpo_sleep_gate.py +0 -0
  245. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_health_liveness.py +0 -0
  246. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_idle_endpoint_reaper.py +0 -0
  247. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_internal_client.py +0 -0
  248. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_kernel_cache.py +0 -0
  249. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_kernel_fingerprint.py +0 -0
  250. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_kv_util.py +0 -0
  251. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_lambda_runner.py +0 -0
  252. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_logging.py +0 -0
  253. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_login_perms.py +0 -0
  254. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_lora_rank_preflight.py +0 -0
  255. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_managed_hf_repo.py +0 -0
  256. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_metrics_schema_agent_contract.py +0 -0
  257. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_mig_guard.py +0 -0
  258. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_multiturn_rollout.py +0 -0
  259. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_oom_escalate_gpu.py +0 -0
  260. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_open_model_policy.py +0 -0
  261. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_orchestrator_flash.py +0 -0
  262. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_packing.py +0 -0
  263. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_poll_helpers.py +0 -0
  264. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_preflight.py +0 -0
  265. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_provider_routing.py +0 -0
  266. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_provider_teardown_robustness.py +0 -0
  267. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_providers_symmetry.py +0 -0
  268. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_realized_cost.py +0 -0
  269. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_resolve_params_b.py +0 -0
  270. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_resume_on_retry.py +0 -0
  271. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_runmgmt.py +0 -0
  272. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_runpod_api_delete.py +0 -0
  273. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_runpod_key_fingerprint.py +0 -0
  274. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_runpod_key_waterfall.py +0 -0
  275. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_runpod_slots.py +0 -0
  276. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_server_api.py +0 -0
  277. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_server_billing.py +0 -0
  278. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_server_db.py +0 -0
  279. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_serving_contract.py +0 -0
  280. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_sft_example_selection.py +0 -0
  281. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_sft_gc_off.py +0 -0
  282. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_thinking_config.py +0 -0
  283. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_train_context_preflight.py +0 -0
  284. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_update_check.py +0 -0
  285. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_vast_api.py +0 -0
  286. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_vast_offers.py +0 -0
  287. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_vast_runner.py +0 -0
  288. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_verifiers.py +0 -0
  289. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_version.py +0 -0
  290. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_wandb_naming.py +0 -0
  291. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_warmstart_cross_repo.py +0 -0
  292. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_weight_cache.py +0 -0
  293. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_worker_dryrun.py +0 -0
  294. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_worker_hardexit.py +0 -0
  295. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_worker_image.py +0 -0
  296. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_worker_init_heartbeat.py +0 -0
  297. {freesolo_flash_dev-0.2.50 → freesolo_flash_dev-0.2.55}/tests/test_worker_thinking.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: freesolo-flash-dev
3
- Version: 0.2.50
3
+ Version: 0.2.55
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
@@ -418,8 +418,18 @@ epochs = 1
418
418
  max_examples = 2
419
419
  lora_rank = 32
420
420
  # kl_penalty_coef = 1.0 # reverse-KL scale
421
+ # opd_eos_loss_coef = 0.5 # terminal-EOS reinforcement; raise if the
422
+ # # student runs past the length cap without
423
+ # # stopping, 0 to disable
421
424
  ```
422
425
 
426
+ The cross-tokenizer reverse-KL is computed over shared decoded-text spans and so **cannot supervise
427
+ the zero-width stop token** — distilling toward a verbose teacher (GLM-5.2) erodes the student's
428
+ termination and rollouts run away to `max_completion_tokens`. `opd_eos_loss_coef` (default 0.5) adds a
429
+ bounded, self-limiting behaviour-cloning term that reinstates the stop signal on rollouts that
430
+ terminated naturally; watch `truncated_rollouts` fall and `mean_eos_logprob` rise in the run metrics.
431
+ Warm-starting from an SFT adapter (which already encodes termination) compounds it.
432
+
423
433
  ---
424
434
 
425
435
  ## GRPO knobs that matter
@@ -472,6 +482,7 @@ targeted fix rather than leaning on the reward gate to slowly select against it.
472
482
  | Repetition / looping collapse | the same phrase repeats until truncation | repetition or length penalty; lower `temperature` |
473
483
  | Overthinking / verbose reasoning | reasoning eats the whole token budget | `thinking_length_penalty_coef`; tighten the prompt |
474
484
  | Completion truncation | answers cut off mid-thought | raise `max_completion_tokens` / `max_context_tokens` |
485
+ | OPD rollouts never stop (high `truncated_rollouts`) | on-policy completions run to the length cap without an EOS; raising the cap barely helps | raise `opd_eos_loss_coef` and warm-start from SFT — the reverse-KL can't supervise the stop token on its own |
475
486
  | Unparsed / over-escaped output | reward can't read the answer | robust parser; return `0.0` on parse fail; format gate |
476
487
  | Wrapper / markdown around structured output | prose around the JSON/answer | a format gate; `stop_sequences` |
477
488
  | Uniform-reward groups | every rollout in a group scores the same → no gradient | shape the reward for partial credit; raise `temperature` |
@@ -227,12 +227,12 @@ def rollout_one_records(
227
227
 
228
228
  - ``generate(prefix_ids, max_new)`` returns an OBJECT (opaque here) exposing ``completion_ids``
229
229
  (``list[int] | None``), ``completion_text`` (``str``), ``truncated`` (``bool``) and ``skip``
230
- (``bool``) — OPD passes its ``_GenResult`` from ``_generate_one`` (termination/trim/U+FFFD gates
231
- already applied), so the record drops straight into the existing teacher-scoring + gkd-loss path.
230
+ (``bool``) — OPD passes its ``_GenResult`` after termination/trim/U+FFFD gates have already been
231
+ applied, so the record drops straight into the existing teacher-scoring + batched-loss path.
232
232
  - Every turn (including a truncated/empty one) is recorded so the caller counts it; a
233
233
  truncated/skip turn ENDS the episode (a student that didn't terminate its turn, or emitted an
234
234
  empty/invalid completion, can't meaningfully continue — and its bad turn is skipped, not
235
- distilled, by the caller's ``_resolve_sample``).
235
+ distilled, by the caller's no-loss sample handling).
236
236
  - ``on_turn_generated`` (optional) fires AFTER each turn's generation to refresh the worker stall
237
237
  clock and advance the sample counter — a many-turn episode is a long serial stretch of GPU
238
238
  generates + teacher-less env steps that would otherwise emit no progress ping.
@@ -70,6 +70,15 @@ class OPDConfig:
70
70
  # (student_logsum - teacher_logsum) advantage. 1.0 is plain reverse KL (Thinking Machines,
71
71
  # *On-Policy Distillation*). Overridable via [train].kl_penalty_coef.
72
72
  kl_coef: float = 1.0
73
+ # Weight of the terminal-EOS behaviour-cloning term added to the reverse-KL loss. The
74
+ # cross-tokenizer text-span alignment CANNOT supervise the stop token — a special/eos token is
75
+ # zero-width (decodes to nothing), so it lands in no alignment group and the reverse-KL gives it
76
+ # NO gradient. Distilling only the content tokens toward a strong, verbose teacher (GLM-5.2) then
77
+ # erodes the student's termination and rollouts run away to the length cap (observed across OPD
78
+ # runs as thousands of non-terminated rollouts). This adds a bounded, self-limiting cross-entropy
79
+ # that pushes up log P(eos) at the position a rollout naturally terminated, restoring the stop
80
+ # signal the alignment cannot express. 0 disables it; overridable via [train].opd_eos_loss_coef.
81
+ eos_loss_coef: float = 0.5
73
82
 
74
83
 
75
84
  @dataclass(frozen=True)
@@ -110,6 +110,19 @@ def opd_rollout_concurrency(prompts_per_step: int = 1, group_size: int = 1) -> i
110
110
  return _positive_int(prompts_per_step, 1) * _positive_int(group_size, 1)
111
111
 
112
112
 
113
+ def opd_loss_microbatch(params_b: float, prompts_per_step: int = 1, group_size: int = 1) -> int:
114
+ """Loss microbatch size used by OPD's dense-logit GKD backward.
115
+
116
+ Keep this in lockstep with worker.opd._opd_loss_microbatch_size without importing the worker from
117
+ the sizing path. Small/medium catalog models run up to four loss samples per forward; 35B-class
118
+ models stay serial for VRAM safety.
119
+ """
120
+ total = opd_rollout_concurrency(prompts_per_step, group_size)
121
+ params = float(params_b or 0.0)
122
+ default = 4 if params and params <= 10.0 else 1
123
+ return max(1, min(total, default))
124
+
125
+
113
126
  def _resident_kv_gb(
114
127
  params_b: float | None, vllm_max_len: int, num_generations: int = 8, fp8_kv: bool = False
115
128
  ) -> float:
@@ -366,11 +379,11 @@ def estimate_vram_gb(
366
379
  # OPD recipe default (thinking uses the longer max_completion_len_thinking). A GRPO-style
367
380
  # min(seq_len, 1024) fallback would UNDER-budget a thinking opd job (1536-token completions).
368
381
  completion = opd_completion_len(max_tokens, thinking)
369
- # run_opd backprops ONE completion at a time (_train_one), so the activations + dense logits
370
- # are SINGLE-sequence NOT sft_per_device(batch_size). The batch-size activation term
371
- # over-budgeted opd and bumped the GPU tier unnecessarily (reported by codex[bot]).
372
- activations = _ACT_COEF * (seq_len / 1024.0) * width
373
- # Dense-logit peak spans BOTH the forward and the loss BACKWARD (gkd_loss has no fused CE):
382
+ # run_opd backprops a small loss microbatch. Budget that actual dense-logit microbatch, not
383
+ # the full rollout/teacher batch and not a single sequence.
384
+ loss_mb = opd_loss_microbatch(params_b, batch_size, group_size)
385
+ activations = loss_mb * _ACT_COEF * (seq_len / 1024.0) * width
386
+ # Dense-logit peak spans BOTH the forward and the loss BACKWARD (OPD has no fused CE):
374
387
  # - forward: bf16 full-sequence logits [seq, vocab] (seq * 2)
375
388
  # fp32 completion rows [completion, vocab] for logsumexp, saved for backward
376
389
  # (completion * 4)
@@ -380,10 +393,10 @@ def estimate_vram_gb(
380
393
  # (seq * 2)
381
394
  # All four coexist at the backward peak. The old formula counted only the two FORWARD buffers,
382
395
  # so a long-completion / large-vocab (248k) opd job under-budgeted the loss backward by
383
- # ~(completion*4 + seq*2)*vocab bytes and could route to a GPU that OOMs in gkd_loss.backward
396
+ # ~(completion*4 + seq*2)*vocab bytes and could route to a GPU that OOMs in OPD loss backward
384
397
  # (codex[bot]). Mirror the SFT dense-logit sizing by budgeting the backward buffers too.
385
- logits_fwd = (seq_len * 2 + completion * 4) * vocab
386
- logits_bwd = (seq_len * 2 + completion * 4) * vocab
398
+ logits_fwd = loss_mb * (seq_len * 2 + completion * 4) * vocab
399
+ logits_bwd = loss_mb * (seq_len * 2 + completion * 4) * vocab
387
400
  logits = (logits_fwd + logits_bwd) / 1e9
388
401
  return base + rollout + activations + logits
389
402
  # Actual TRL SFT keeps fused CE disabled (see worker/sft.py), so dense logits materialize even
@@ -577,6 +590,7 @@ def model_required_vram_gb(
577
590
  use_vllm: bool = True,
578
591
  vocab: int = _VOCAB_DEFAULT,
579
592
  active_params_b: float | None = None,
593
+ fp8_kv: bool = False,
580
594
  ) -> int:
581
595
  est = estimate_vram_gb(
582
596
  params_b,
@@ -591,10 +605,45 @@ def model_required_vram_gb(
591
605
  use_vllm=use_vllm,
592
606
  vocab=vocab,
593
607
  active_params_b=active_params_b,
608
+ fp8_kv=fp8_kv,
594
609
  sft_fused_ce=sft_fused_ce,
595
610
  )
596
611
  return math.ceil(est * headroom)
597
612
 
613
+ def _opd_fp8_adjust(
614
+ need: int,
615
+ params_b: float,
616
+ *,
617
+ quant: str = "bf16",
618
+ use_vllm: bool = True,
619
+ vocab: int = _VOCAB_DEFAULT,
620
+ active_params_b: float | None = None,
621
+ ) -> int:
622
+ """Re-size an OPD requirement with an fp8 KV cache once the run is provably modern-card-only.
623
+
624
+ The colocated OPD vLLM rollout engine reserves an fp8 KV cache on cc >= 8.9 hardware
625
+ (engine/worker/opd_vllm.py), but the estimate defaults to a bf16 KV pool. Any OPD run needing
626
+ more VRAM than the biggest non-fp8 validated card (the 80 GB A100) can ONLY land on a modern
627
+ (cc >= 8.9) card, so that bf16 pool is a phantom: it doubles the real KV and wrongly rejects
628
+ full-context / grouped OPD configs on the 35B that actually fit a B200. Halve it — but only
629
+ while the fp8-sized requirement still clears the non-fp8 ceiling, so the discount can never
630
+ pull the run back onto a card that would NOT use fp8 (which would then OOM)."""
631
+ from flash.providers.base import max_non_fp8_kv_vram_gb
632
+
633
+ ceiling = max_non_fp8_kv_vram_gb()
634
+ if need <= ceiling:
635
+ return need
636
+ fp8_need = _need(
637
+ params_b,
638
+ "opd",
639
+ quant=quant,
640
+ use_vllm=use_vllm,
641
+ vocab=vocab,
642
+ active_params_b=active_params_b,
643
+ fp8_kv=True,
644
+ )
645
+ return fp8_need if fp8_need > ceiling else need
646
+
598
647
  from flash.catalog import MODELS, vocab_size_for
599
648
 
600
649
  info = MODELS.get(model_id)
@@ -624,6 +673,15 @@ def model_required_vram_gb(
624
673
  vocab=model_vocab,
625
674
  active_params_b=active_b,
626
675
  )
676
+ if is_opd:
677
+ need = _opd_fp8_adjust(
678
+ need,
679
+ params_b or 4.0,
680
+ quant=quant,
681
+ use_vllm=use_vllm,
682
+ vocab=model_vocab,
683
+ active_params_b=active_b,
684
+ )
627
685
  floor = 0
628
686
  if is_grpo and getattr(info, "grpo_min_vram_gb", 0):
629
687
  floor = int(info.grpo_min_vram_gb)
@@ -689,6 +747,8 @@ def model_required_vram_gb(
689
747
  # open-model OPD uses its own dense-logit + colocated-vLLM estimator. The grpo-only sequence
690
748
  # escalation stays gated on is_grpo.
691
749
  need = _need(params_b, algorithm, vocab=model_vocab)
750
+ if is_opd:
751
+ need = _opd_fp8_adjust(need, params_b, vocab=model_vocab)
692
752
  if is_grpo:
693
753
  need += grpo_seq_escalation_gb(params_b, seq_len)
694
754
  if is_vllm_rollout:
@@ -21,6 +21,7 @@ from flash.engine.worker.adapter import (
21
21
  _init_adapter_model,
22
22
  _resolve_adapter_ref,
23
23
  make_lora,
24
+ prepare_fresh_lora_base,
24
25
  recombined_warmstart_adapter_dir,
25
26
  require_vllm_for_rollout_func,
26
27
  )
@@ -87,7 +88,6 @@ from flash.engine.worker.lora import (
87
88
  is_vl_checkpoint,
88
89
  patch_grpo_mask_aware_lm_head,
89
90
  recombine_lora_adapters,
90
- strip_language_model_infix,
91
91
  )
92
92
  from flash.engine.worker.opd import run_opd
93
93
  from flash.engine.worker.perf import (
@@ -447,6 +447,7 @@ __all__ = [
447
447
  "patch_grpo_mask_aware_lm_head",
448
448
  "patch_trl_colocate_llm_kwargs",
449
449
  "prefetch_model",
450
+ "prepare_fresh_lora_base",
450
451
  "prompt_opens_thinking",
451
452
  "publish_deployable_checkpoint",
452
453
  "recombine_lora_adapters",
@@ -460,7 +461,6 @@ __all__ = [
460
461
  "run_rl",
461
462
  "run_sft",
462
463
  "setup_perf_backends",
463
- "strip_language_model_infix",
464
464
  "strip_think",
465
465
  "think_token_count",
466
466
  "thinking_text",
@@ -12,6 +12,7 @@ from flash.engine.worker.lora import (
12
12
  assert_adapter_delta_nonzero,
13
13
  assert_adapter_load_clean,
14
14
  assert_lora_applied,
15
+ is_vl_checkpoint,
15
16
  recombine_lora_adapters,
16
17
  validate_recombined_lora_rank,
17
18
  )
@@ -20,7 +21,7 @@ from flash.engine.worker.perf import optimal_attn_impl
20
21
 
21
22
  def make_lora(model_id: str | None = None):
22
23
  """Build LoRA config targeting all linear layers (VL models included: the vision tower /
23
- projector / MTP linears are adapted too; on text-only data they simply get no gradient)."""
24
+ projector / MTP linears are adapted too; on examples without images they simply get no gradient)."""
24
25
  from peft import LoraConfig
25
26
 
26
27
  targets = "all-linear"
@@ -43,6 +44,38 @@ def make_lora(model_id: str | None = None):
43
44
  return LoraConfig(**kwargs)
44
45
 
45
46
 
47
+ def prepare_fresh_lora_base(
48
+ model_source: str,
49
+ model_id: str,
50
+ model_init_kwargs: dict,
51
+ *,
52
+ force: bool = False,
53
+ phase: str = "train",
54
+ ):
55
+ """Prepare the correct base object/path for fresh-LoRA training.
56
+
57
+ PEFT expands ``target_modules`` against the concrete model object it wraps. TRL's default string
58
+ loader can resolve Qwen VL checkpoints to a language-only tree, while OPD and serving use the full
59
+ image-text tree. Therefore every fresh LoRA on a VL checkpoint must preload
60
+ ``AutoModelForImageTextToText`` so SFT/GRPO/OPD adapter tensor sets stay identical, including
61
+ zero-gradient vision-tower LoRA tensors on examples without images. Non-VL checkpoints keep the original
62
+ model path for TRL's normal loader. ``force`` is used after a VL warm-start merge already proved
63
+ the source adapter is VL, so a transient config-probe failure cannot send the fresh stage back to
64
+ a language-only loader.
65
+ """
66
+ if not (force or is_vl_checkpoint(model_id)):
67
+ return model_source
68
+ from transformers import AutoModelForImageTextToText
69
+
70
+ print(
71
+ f"[{phase}] VL checkpoint: loading full multimodal model for fresh LoRA target parity "
72
+ f"({model_source})"
73
+ )
74
+ return AutoModelForImageTextToText.from_pretrained(
75
+ model_source, trust_remote_code=True, **model_init_kwargs
76
+ )
77
+
78
+
46
79
  def require_vllm_for_rollout_func(use_rollout_func: bool, use_vllm: bool, model_id: str) -> None:
47
80
  """Fail fast when multi-turn GRPO needs colocated vLLM but it's disabled."""
48
81
  if use_rollout_func and not use_vllm:
@@ -65,10 +98,10 @@ def _merge_vl_warmstart_adapter(adir: str, model_id: str, attn_kw: dict) -> str:
65
98
  """VL warm-start (#296): MERGE the SFT into the FULL multimodal base and save the merged model to a
66
99
  fresh temp dir — the new training base for a GRPO LoRA trained from scratch. Continuing the live SFT
67
100
  LoRA instead makes the colocated vLLM rollout AND KL reference run off the BARE base (the SFT only
68
- reaches vLLM via a text<->VL weight-sync that round-trips poorly for ``*ForConditionalGeneration``
101
+ reaches vLLM via a language/VL weight-sync that round-trips poorly for ``*ForConditionalGeneration``
69
102
  models), so GRPO rolls out base-verbose and collapses a working SFT back to base (observed: every
70
103
  Qwen3.5 GRPO reverts; non-VL MiniCPM does not). Merging into the full multimodal model (NOT the
71
- text-only tree) keeps the VL config + ``language_model.*`` keys that both the trainer reload and
104
+ language-only tree) keeps the VL config + ``language_model.*`` keys that both the trainer reload and
72
105
  vLLM's VL loader expect; the SFT keys match here WITHOUT the infix strip. Records the SFT dir for the
73
106
  finalize recombine and returns the merged dir."""
74
107
  import gc
@@ -10,9 +10,9 @@ CPU-importable.
10
10
  from __future__ import annotations
11
11
 
12
12
  # Natively-multimodal model types (Qwen3.5/3.6). Their LoRA adapters adapt the FULL module
13
- # tree — vision tower / projector / MTP head included, like every other linear (on text-only
13
+ # tree — vision tower / projector / MTP head included, like every other linear (on no-image
14
14
  # data those get no gradient, so their lora_B stays zero-init). The engine loads and serves
15
- # the whole VL model (vision tower included); there is no text-only / language_model_only path.
15
+ # the whole VL model (vision tower included); there is no language-only VL adapter path.
16
16
  _VL_MODEL_TYPES = ("qwen3_5", "qwen3_5_moe", "qwen3_6")
17
17
 
18
18
 
@@ -171,36 +171,13 @@ def disable_liger_grpo_torch_compile(trainer) -> bool:
171
171
  # --------------------------------------------------------------------------------------------
172
172
  # Warm-start (init_from_adapter) SFT-adapter key namespace for VL checkpoints.
173
173
  #
174
- # SFT (run_sft) trains the FULL multimodal model: ``SFTTrainer(model=model_id,
175
- # peft_config=make_lora(...))`` loads ``Qwen3_5ForConditionalGeneration`` whose LM modules live
176
- # under ``language_model.``, so the SAVED adapter's keys are
177
- # ``base_model.model.model.language_model.layers.X...``. Warm-started GRPO (``_init_adapter_model``)
178
- # loads the base via ``AutoModelForCausalLM``; when that resolves to the TEXT-ONLY module tree its
179
- # LoRA targets are named ``base_model.model.model.layers.X...`` (no ``language_model.`` infix), so
180
- # the two adapters name the SAME modules differently. ``strip_language_model_infix`` lines them up
181
- # for the ``recombine_lora_adapters`` SFT⊕GRPO stacking (deploy-time), then the recombine re-emits
182
- # the serving ``language_model`` namespace. ``_LANGUAGE_MODEL_INFIX`` is also the signal
183
- # ``adapter_is_vl_warmstart`` reads to detect a VL warm-start adapter from its keys.
174
+ # SFT/GRPO/OPD now force fresh LoRA training for VL checkpoints through the FULL multimodal model so
175
+ # their module sets match exactly. ``_LANGUAGE_MODEL_INFIX`` is the signal
176
+ # ``adapter_is_vl_warmstart`` reads to detect a full-VL warm-start adapter from its keys.
184
177
 
185
178
  _LANGUAGE_MODEL_INFIX = ".language_model."
186
179
 
187
180
 
188
- def strip_language_model_infix(key: str) -> str:
189
- """Strip the FIRST ``.language_model.`` infix from a peft adapter weight key.
190
-
191
- ``base_model.model.model.language_model.layers.0.linear_attn.out_proj.lora_A.default.weight``
192
- -> ``base_model.model.model.layers.0.linear_attn.out_proj.lora_A.default.weight``.
193
-
194
- Only the first occurrence is removed (the LM-vs-VL boundary appears once in the path); keys
195
- without the infix are returned unchanged.
196
- """
197
- i = key.find(_LANGUAGE_MODEL_INFIX)
198
- if i == -1:
199
- return key
200
- # Replace ".language_model." with "." (keep one separator dot).
201
- return key[:i] + "." + key[i + len(_LANGUAGE_MODEL_INFIX) :]
202
-
203
-
204
181
  # Substrings that identify a peft LoRA weight key (vs a base-model param). The whole adapter file
205
182
  # is LoRA weights, but a wrong-arch / corrupt checkpoint can contain non-LoRA tensors, so we filter.
206
183
  _LORA_KEY_MARKERS = (".lora_A.", ".lora_B.", ".lora_embedding_A.", ".lora_embedding_B.", "lora_")
@@ -219,7 +196,7 @@ _MAX_SAFETENSORS_HEADER_BYTES = 100 * 1024 * 1024
219
196
  def _read_adapter_tensor_keys(adir: str) -> list[str] | None:
220
197
  """Tensor key names in the downloaded adapter.
221
198
 
222
- For safetensors, read ONLY the JSON header (pure stdlib, no tensor data). For legacy PEFT
199
+ For safetensors, read ONLY the JSON header (pure stdlib, no tensor data). For PEFT
223
200
  ``adapter_model.bin``, use Torch's weights-only loader and inspect the state-dict keys. Returns
224
201
  ``None`` when no adapter weights exist in ``adir``.
225
202
  """
@@ -292,12 +269,12 @@ def adapter_is_vl_warmstart(adir: str, model_id: str) -> bool:
292
269
 
293
270
  Robust to a transient ``is_vl_checkpoint`` config-probe failure (it calls
294
271
  ``AutoConfig.from_pretrained`` and swallows EVERY exception to return False, so an HF
295
- rate-limit / network hiccup / uncached config could silently route a genuine VL warm-start down
296
- the text-only path and reintroduce the trainer<->vLLM mismatch — issue #286). An adapter that
297
- actually carries ``.language_model.`` LoRA keys was saved against the full multimodal model and
272
+ rate-limit / network hiccup / uncached config could silently route a genuine VL warm-start away
273
+ from the full-VL merge path and reintroduce the trainer<->vLLM mismatch — issue #286). An adapter
274
+ that actually carries ``.language_model.`` LoRA keys was saved against the full multimodal model and
298
275
  IS a VL warm-start regardless of the probe (the adapter's own keys are the authoritative signal).
299
276
  Falls back to the config probe only when the adapter can't be read or carries no
300
- ``.language_model.`` LoRA keys (already-text-only / non-VL)."""
277
+ ``.language_model.`` LoRA keys."""
301
278
  try:
302
279
  keys = _read_adapter_tensor_keys(adir)
303
280
  if keys and any(_LANGUAGE_MODEL_INFIX in k for k in keys if _is_lora_key(k)):
@@ -450,39 +427,6 @@ def recombine_lora_adapters(
450
427
  old_cfg, old_sd = _load(old_dir)
451
428
  new_cfg, new_sd = _load(new_dir)
452
429
 
453
- # Normalize the ``.language_model.`` infix on BOTH adapters' keys before comparing/stacking.
454
- # The VL merge warm-start (#296) trains the prior adapter against the FULL multimodal model, so the
455
- # warm-start adapter's keys carry the infix (``base_model.model.model.language_model.layers...``),
456
- # while a fresh LoRA saved by the text-only ``AutoModelForCausalLM`` trainer has no infix
457
- # (``base_model.model.model.layers...``). Without this, the equivalent LM modules compare as
458
- # DIFFERENT targets and the recombine wrongly aborts. The normalized form is only an INTERNAL math
459
- # key: if either source adapter used the VL ``language_model`` namespace for a tensor, the
460
- # recombined output uses that same serving-compatible key instead of the stripped text-only key.
461
- def _normalize_infix(sd, which):
462
- norm: dict = {}
463
- infixed_keys: dict[str, str] = {}
464
- for k, v in sd.items():
465
- nk = strip_language_model_infix(k)
466
- # Fail closed on ANY post-normalization collision. A malformed adapter carrying BOTH the
467
- # infixed and the already-text-only form of a key would otherwise let the second write
468
- # silently overwrite the first (the text-only key has nk == k, so a ``nk != k`` guard
469
- # would skip it) — recombining whichever duplicate wins instead of rejecting the mix.
470
- if nk in norm:
471
- raise ValueError(
472
- f"recombine: {which} adapter key {k!r} collides with another after stripping "
473
- "the '.language_model.' infix — cannot normalize a mixed VL adapter"
474
- )
475
- norm[nk] = v
476
- if _LANGUAGE_MODEL_INFIX in k:
477
- infixed_keys[nk] = k
478
- return norm, infixed_keys
479
-
480
- old_sd, old_infixed_keys = _normalize_infix(old_sd, "warm-start")
481
- new_sd, new_infixed_keys = _normalize_infix(new_sd, "fresh")
482
-
483
- def _output_key(k: str) -> str:
484
- return old_infixed_keys.get(k) or new_infixed_keys.get(k) or k
485
-
486
430
  for name, cfg in (("warm-start", old_cfg), ("fresh", new_cfg)):
487
431
  # peft_type defaults to LORA for older configs that omit it; anything else (e.g. ADALORA)
488
432
  # has different tensor/scale semantics the cat-recombine math doesn't model.
@@ -583,15 +527,8 @@ def recombine_lora_adapters(
583
527
  f"recombine: lora_A key {ak!r} has no matching lora_B key {bk!r} — the adapter is "
584
528
  "malformed (unpaired LoRA tensors)"
585
529
  )
586
- out_ak = _output_key(ak)
587
- out_bk = _output_key(bk)
588
- if out_ak in out or out_bk in out:
589
- raise ValueError(
590
- "recombine: output key collision while restoring the '.language_model.' infix "
591
- f"(A={out_ak!r}, B={out_bk!r})"
592
- )
593
530
  # A: (r, in_features) — stacked along the rank axis (no scaling; scale lives on B).
594
- out[out_ak] = torch.cat([old_sd[ak], new_sd[ak]], dim=0).contiguous()
531
+ out[ak] = torch.cat([old_sd[ak], new_sd[ak]], dim=0).contiguous()
595
532
  # B: (out_features, r) — bake each adapter's own scale, then stack along the rank axis. The
596
533
  # combined adapter carries unit scale, so the per-component scales survive intact.
597
534
  # Promote to the higher of the two input dtypes (don't force the warm-start's, which would
@@ -600,16 +537,7 @@ def recombine_lora_adapters(
600
537
  dt = torch.promote_types(old_sd[bk].dtype, new_sd[bk].dtype)
601
538
  b_old = old_sd[bk].to(torch.float32) * s_old
602
539
  b_new = new_sd[bk].to(torch.float32) * s_new
603
- out[out_bk] = torch.cat([b_old, b_new], dim=1).to(dt).contiguous()
604
-
605
- if old_infixed_keys or new_infixed_keys:
606
- plain_layer_keys = sorted(k for k in out if k.startswith("base_model.model.model.layers."))
607
- if plain_layer_keys:
608
- raise ValueError(
609
- "recombine: VL warm-start output would use plain model.layers LoRA keys "
610
- f"(e.g. {plain_layer_keys[:3]}). Serving expects the language_model namespace; "
611
- "refusing to write a known-bad deploy artifact."
612
- )
540
+ out[bk] = torch.cat([b_old, b_new], dim=1).to(dt).contiguous()
613
541
 
614
542
  out_cfg = dict(new_cfg)
615
543
  out_cfg["r"] = r_out
@@ -619,7 +547,7 @@ def recombine_lora_adapters(
619
547
  out_cfg["alpha_pattern"] = {}
620
548
  # The fresh adapter was trained on the ephemeral warm-start-merged temp dir, so out_cfg (copied
621
549
  # from new_cfg) still names that temp path as base_model_name_or_path. Replace it with the real
622
- # catalog base from the warm-start config; if that config carries none (e.g. an external/legacy
550
+ # catalog base from the warm-start config; if that config carries none (e.g. an external
623
551
  # adapter), DROP the field rather than ship a deployed config pointing at a now-deleted temp dir.
624
552
  old_base = old_cfg.get("base_model_name_or_path")
625
553
  if old_base: