freesolo-flash-dev 0.2.45__tar.gz → 0.2.47__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 (294) hide show
  1. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/PKG-INFO +2 -2
  2. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/cli/commands.py +28 -21
  3. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/cli/training_doc.py +12 -6
  4. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/cost/analytical.py +11 -9
  5. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/cost/facts.py +4 -7
  6. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/cost/spec.py +0 -3
  7. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/cost/types.py +7 -7
  8. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/multiturn_rollout.py +174 -42
  9. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/recipe.py +2 -3
  10. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/__init__.py +6 -0
  11. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/gpu_setup.py +46 -0
  12. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/heartbeat.py +26 -6
  13. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/hf.py +48 -20
  14. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/opd.py +148 -42
  15. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/rl.py +11 -2
  16. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/sft.py +6 -1
  17. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/_worker.py +6 -0
  18. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/schema/__init__.py +5 -37
  19. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/spec.py +0 -3
  20. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/pyproject.toml +3 -3
  21. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_algorithms.py +3 -2
  22. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_cli_commands.py +40 -12
  23. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_cost_analytical.py +23 -0
  24. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_flash_worker.py +33 -0
  25. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_multiturn_rollout.py +101 -0
  26. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_opd.py +267 -23
  27. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_worker_init_heartbeat.py +41 -1
  28. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/uv.lock +1 -1
  29. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/.dockerignore +0 -0
  30. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/.env.example +0 -0
  31. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/.github/workflows/auto-rebake.yml +0 -0
  32. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/.github/workflows/bake-kernel-cache.yml +0 -0
  33. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/.github/workflows/ci.yml +0 -0
  34. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/.github/workflows/main-source-guard.yml +0 -0
  35. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/.github/workflows/notify-tests-repo.yml +0 -0
  36. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/.github/workflows/publish-dev.yml +0 -0
  37. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/.github/workflows/publish-image.yml +0 -0
  38. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/.github/workflows/publish.yml +0 -0
  39. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/.github/workflows/version-parity.yml +0 -0
  40. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/.github/workflows/worker-image.yml +0 -0
  41. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/.gitignore +0 -0
  42. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/Dockerfile +0 -0
  43. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/Dockerfile.worker +0 -0
  44. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/LICENSE +0 -0
  45. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/README.md +0 -0
  46. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/build/kernel_cache/.gitignore +0 -0
  47. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/build/kernel_cache/.keep +0 -0
  48. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/docker/Dockerfile.kernelcache +0 -0
  49. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/docker/Dockerfile.kernelcache.relayer +0 -0
  50. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/docker/bake_kernel_cache.py +0 -0
  51. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/docker/bake_pod_entry.py +0 -0
  52. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/docker/kernel_fingerprint.py +0 -0
  53. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/docker/make_rp_handler.py +0 -0
  54. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/__init__.py +0 -0
  55. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/_channel.py +0 -0
  56. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/_fileio.py +0 -0
  57. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/_logging.py +0 -0
  58. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/_update_check.py +0 -0
  59. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/adapter_artifacts.py +0 -0
  60. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/catalog.py +0 -0
  61. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/cli/__init__.py +0 -0
  62. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/cli/__main__.py +0 -0
  63. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/cli/_tty.py +0 -0
  64. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/cli/envpush.py +0 -0
  65. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/cli/render.py +0 -0
  66. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/client/__init__.py +0 -0
  67. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/client/config.py +0 -0
  68. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/client/http.py +0 -0
  69. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/client/runtime_secrets.py +0 -0
  70. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/client/specs.py +0 -0
  71. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/cost/__init__.py +0 -0
  72. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/__init__.py +0 -0
  73. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/accounting.py +0 -0
  74. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/chalk_kernels.py +0 -0
  75. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/vram.py +0 -0
  76. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/__main__.py +0 -0
  77. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/_pkg.py +0 -0
  78. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/adapter.py +0 -0
  79. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/decoding.py +0 -0
  80. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/finalize.py +0 -0
  81. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/grpo.py +0 -0
  82. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/kernel_warmup.py +0 -0
  83. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/lora.py +0 -0
  84. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/opd_gkd.py +0 -0
  85. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/packing.py +0 -0
  86. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/perf/__init__.py +0 -0
  87. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/perf/attn.py +0 -0
  88. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/perf/diagnostics.py +0 -0
  89. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/perf/lifecycle.py +0 -0
  90. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/perf/liger.py +0 -0
  91. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/perf/loraplus.py +0 -0
  92. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/perf/memory.py +0 -0
  93. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/teacher.py +0 -0
  94. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/tokenizer_align.py +0 -0
  95. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/engine/worker/wandb_log.py +0 -0
  96. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/envs/__init__.py +0 -0
  97. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/envs/adapter.py +0 -0
  98. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/envs/archive_policy.py +0 -0
  99. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/envs/base.py +0 -0
  100. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/envs/loader.py +0 -0
  101. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/envs/pull.py +0 -0
  102. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/envs/registry.py +0 -0
  103. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/lora_rank.py +0 -0
  104. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/__init__.py +0 -0
  105. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/_auth.py +0 -0
  106. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/_hf_artifacts.py +0 -0
  107. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/_hf_retry.py +0 -0
  108. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/_http.py +0 -0
  109. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/_instance.py +0 -0
  110. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/_instance_bootstrap.py +0 -0
  111. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/_instance_poll.py +0 -0
  112. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/_instance_provider.py +0 -0
  113. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/_poll.py +0 -0
  114. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/allocator.py +0 -0
  115. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/base.py +0 -0
  116. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/lambdalabs/__init__.py +0 -0
  117. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/lambdalabs/api.py +0 -0
  118. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/lambdalabs/auth.py +0 -0
  119. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/lambdalabs/gpus.py +0 -0
  120. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/lambdalabs/jobs/__init__.py +0 -0
  121. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/lambdalabs/jobs/builders.py +0 -0
  122. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/lambdalabs/preflight.py +0 -0
  123. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/lambdalabs/pricing.py +0 -0
  124. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/preflight.py +0 -0
  125. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/realized.py +0 -0
  126. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/runpod/__init__.py +0 -0
  127. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/runpod/api.py +0 -0
  128. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/runpod/auth.py +0 -0
  129. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/runpod/cost.py +0 -0
  130. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/runpod/gpus.py +0 -0
  131. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/runpod/jobs.py +0 -0
  132. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/runpod/keys.py +0 -0
  133. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/runpod/preflight.py +0 -0
  134. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/runpod/preload.py +0 -0
  135. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/runpod/pricing.py +0 -0
  136. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/runpod/slots.py +0 -0
  137. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/runpod/train/__init__.py +0 -0
  138. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/runpod/train/deps.py +0 -0
  139. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/runpod/train/endpoints.py +0 -0
  140. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/vast/__init__.py +0 -0
  141. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/vast/api.py +0 -0
  142. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/vast/auth.py +0 -0
  143. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/vast/jobs/__init__.py +0 -0
  144. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/vast/jobs/builders.py +0 -0
  145. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/vast/preflight.py +0 -0
  146. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/providers/vast/pricing.py +0 -0
  147. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/py.typed +0 -0
  148. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/runner/__init__.py +0 -0
  149. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/runner/checkpoints.py +0 -0
  150. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/runner/deploy.py +0 -0
  151. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/runner/lifecycle.py +0 -0
  152. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/schema/fields.py +0 -0
  153. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/serve/__init__.py +0 -0
  154. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/serve/deploy.py +0 -0
  155. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/serve/export.py +0 -0
  156. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/serve/pricing.py +0 -0
  157. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/server/__init__.py +0 -0
  158. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/server/__main__.py +0 -0
  159. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/server/_deps.py +0 -0
  160. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/server/_internal_client.py +0 -0
  161. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/server/_locks.py +0 -0
  162. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/server/_runtime.py +0 -0
  163. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/server/app.py +0 -0
  164. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/server/auth.py +0 -0
  165. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/server/billing.py +0 -0
  166. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/server/billing_retry.py +0 -0
  167. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/server/checkpoints.py +0 -0
  168. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/server/db.py +0 -0
  169. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/server/environment_registry.py +0 -0
  170. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/server/envs.py +0 -0
  171. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/server/reconcile.py +0 -0
  172. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/server/routes/__init__.py +0 -0
  173. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/server/routes/envs.py +0 -0
  174. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/server/routes/meta.py +0 -0
  175. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/server/routes/runs.py +0 -0
  176. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/server/routes/serving.py +0 -0
  177. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/flash/server/run_registry.py +0 -0
  178. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/infisical-entrypoint.sh +0 -0
  179. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/scripts/build_dev_dist.py +0 -0
  180. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/__init__.py +0 -0
  181. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/_helpers/__init__.py +0 -0
  182. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/_helpers/runner.py +0 -0
  183. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/_helpers/specs.py +0 -0
  184. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/_helpers/vast.py +0 -0
  185. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/conftest.py +0 -0
  186. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/fixtures/math_eval.jsonl +0 -0
  187. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/fixtures/math_train.jsonl +0 -0
  188. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/live/__init__.py +0 -0
  189. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/live/conftest.py +0 -0
  190. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/live/test_lambda_live.py +0 -0
  191. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/live/test_runpod_live.py +0 -0
  192. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/live/test_vast_live.py +0 -0
  193. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_agent_flash_cli_contract.py +0 -0
  194. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_allocator.py +0 -0
  195. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_b200_rollout_opt.py +0 -0
  196. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_backend_jobspec_contract.py +0 -0
  197. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_billing_retry.py +0 -0
  198. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_cancel_remote.py +0 -0
  199. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_catalog_consistency.py +0 -0
  200. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_chalk_kernels.py +0 -0
  201. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_charge_pricing.py +0 -0
  202. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_checkpoints.py +0 -0
  203. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_cli_errors.py +0 -0
  204. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_cli_estimate.py +0 -0
  205. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_cli_help.py +0 -0
  206. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_cli_managed.py +0 -0
  207. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_cli_render_theme.py +0 -0
  208. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_client.py +0 -0
  209. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_client_server_integration.py +0 -0
  210. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_config_overrides.py +0 -0
  211. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_cost_equation.py +0 -0
  212. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_cost_estimate.py +0 -0
  213. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_cost_hardware.py +0 -0
  214. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_cost_models.py +0 -0
  215. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_cost_rewards.py +0 -0
  216. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_dev_channel.py +0 -0
  217. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_disk_gb.py +0 -0
  218. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_endpoint_name.py +0 -0
  219. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_env_cache_evict.py +0 -0
  220. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_env_delete.py +0 -0
  221. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_env_download.py +0 -0
  222. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_env_download_api.py +0 -0
  223. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_env_no_install.py +0 -0
  224. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_env_publish.py +0 -0
  225. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_env_pull.py +0 -0
  226. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_env_pull_managed_control_plane.py +0 -0
  227. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_env_push.py +0 -0
  228. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_env_rate_limit_resolve.py +0 -0
  229. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_envs_coverage.py +0 -0
  230. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_export.py +0 -0
  231. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_flash_mvp.py +0 -0
  232. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_flashinfer_cache_dirs.py +0 -0
  233. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_github_urlopen_retry.py +0 -0
  234. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_gpus.py +0 -0
  235. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_grpo_mask_aware.py +0 -0
  236. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_grpo_params.py +0 -0
  237. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_grpo_sleep_gate.py +0 -0
  238. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_health_liveness.py +0 -0
  239. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_idle_endpoint_reaper.py +0 -0
  240. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_internal_client.py +0 -0
  241. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_jobs.py +0 -0
  242. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_kernel_cache.py +0 -0
  243. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_kernel_fingerprint.py +0 -0
  244. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_kv_util.py +0 -0
  245. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_lambda_runner.py +0 -0
  246. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_logging.py +0 -0
  247. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_login_perms.py +0 -0
  248. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_lora_rank_preflight.py +0 -0
  249. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_managed_hf_repo.py +0 -0
  250. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_metrics_schema_agent_contract.py +0 -0
  251. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_mig_guard.py +0 -0
  252. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_oom_escalate_gpu.py +0 -0
  253. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_open_model_policy.py +0 -0
  254. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_orchestrator_flash.py +0 -0
  255. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_packing.py +0 -0
  256. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_poll_helpers.py +0 -0
  257. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_preflight.py +0 -0
  258. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_provider_routing.py +0 -0
  259. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_provider_teardown_robustness.py +0 -0
  260. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_providers_symmetry.py +0 -0
  261. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_realized_cost.py +0 -0
  262. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_resolve_params_b.py +0 -0
  263. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_resume_on_retry.py +0 -0
  264. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_runmgmt.py +0 -0
  265. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_runpod_api_delete.py +0 -0
  266. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_runpod_key_fingerprint.py +0 -0
  267. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_runpod_key_waterfall.py +0 -0
  268. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_runpod_slots.py +0 -0
  269. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_serve.py +0 -0
  270. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_server_api.py +0 -0
  271. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_server_billing.py +0 -0
  272. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_server_db.py +0 -0
  273. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_serving_contract.py +0 -0
  274. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_sft_example_selection.py +0 -0
  275. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_sft_gc_off.py +0 -0
  276. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_spec_and_validation.py +0 -0
  277. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_thinking_config.py +0 -0
  278. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_train_context_preflight.py +0 -0
  279. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_update_check.py +0 -0
  280. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_vast_api.py +0 -0
  281. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_vast_offers.py +0 -0
  282. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_vast_runner.py +0 -0
  283. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_verifiers.py +0 -0
  284. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_version.py +0 -0
  285. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_vl_warmstart_adapter_keys.py +0 -0
  286. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_vl_warmstart_recombine.py +0 -0
  287. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_wandb_naming.py +0 -0
  288. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_warmstart_cross_repo.py +0 -0
  289. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_weight_cache.py +0 -0
  290. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_worker_dryrun.py +0 -0
  291. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_worker_hardexit.py +0 -0
  292. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_worker_image.py +0 -0
  293. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_worker_stack.py +0 -0
  294. {freesolo_flash_dev-0.2.45 → freesolo_flash_dev-0.2.47}/tests/test_worker_thinking.py +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: freesolo-flash-dev
3
- Version: 0.2.45
4
- Summary: Flash — managed LoRA post-training (SFT/GRPO) for Freesolo environments, driven by the `flash` CLI
3
+ Version: 0.2.47
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
7
7
  Author: Freesolo
@@ -210,14 +210,14 @@ A managed run should use the returned [environment] id from
210
210
  This starter implements a tiny "guess the secret number" game so you can see the
211
211
  episode hooks wired end-to-end. Replace it with your real task before a real run.
212
212
 
213
- SFT and GRPO train off this file; OPD (configs/opd.toml) does NOT support multi-turn:
213
+ All three algorithms train off this file:
214
214
  - GRPO (configs/rl.toml) rolls out full episodes and optimizes `score_episode`.
215
215
  - SFT (configs/sft.toml) learns the gold trajectory. Provide it per row as
216
216
  `output = {"messages": [...]}` (a full assistant/tool trajectory) or a scalar
217
217
  `output` for a single gold assistant turn.
218
- - OPD (configs/opd.toml) distils one sampled completion per prompt and cannot drive the
219
- turn loop, so it is single-turn only `flash train configs/opd.toml` fails fast here.
220
- Use OPD with a single-turn env (`flash env setup --single-turn`).
218
+ - OPD (configs/opd.toml) rolls out each episode and distils EVERY assistant turn against
219
+ the Fireworks GLM teacher, conditioned on the transcript so far the multi-turn
220
+ on-policy-distillation objective. The teacher key is platform-managed (nothing to set).
221
221
  """
222
222
 
223
223
  from __future__ import annotations
@@ -492,32 +492,27 @@ def cmd_env_setup(args) -> int:
492
492
  )
493
493
  opd = Path("configs/opd.toml")
494
494
  if not opd.exists():
495
- # opd (on-policy distillation) is SINGLE-TURN only: it samples one completion per prompt and
496
- # cannot drive a multi-turn/tool episode loop, so `flash train configs/opd.toml` FAILS FAST on a
497
- # multi-turn env (run_opd rejects it at setup). Scaffold it in both modes for reference, but say
498
- # so loudly in the multi-turn one so the config isn't a footgun.
495
+ # opd (on-policy distillation) works in BOTH modes: single-turn distils one sampled completion
496
+ # per prompt; multi-turn rolls out each episode and distils every assistant turn against the
497
+ # transcript so far. The scaffold differs only by a one-line note pointing at the mode.
499
498
  opd_multiturn_note = (
500
- "# NOTE: opd is SINGLE-TURN only it distils one sampled completion per prompt and cannot\n"
501
- "# drive this multi-turn environment's episode loop, so `flash train configs/opd.toml` will\n"
502
- "# fail fast here. Use configs/rl.toml (grpo) or configs/sft.toml for multi-turn; keep opd for\n"
503
- "# a single-turn env (`flash env setup --single-turn`).\n\n"
499
+ "# NOTE: opd rolls out each episode and distils EVERY assistant turn (conditioned on the\n"
500
+ "# transcript so far) against the Fireworks GLM teacher the multi-turn distillation path.\n\n"
504
501
  if multi_turn
505
502
  else ""
506
503
  )
507
504
  opd.write_text(
508
505
  f"{opd_multiturn_note}"
509
506
  'model = "Qwen/Qwen3.5-4B"\n'
510
- 'algorithm = "opd" # on-policy distillation from a Fireworks GLM teacher (single-turn only)\n\n'
507
+ 'algorithm = "opd" # on-policy distillation from the managed GLM 5.2 teacher\n\n'
511
508
  "# Environment: upload this project folder with\n"
512
509
  "# `flash env push --name my-env .`, then paste the returned id below.\n"
513
- "# FIREWORKS_API_KEY (the GLM teacher key) is read from your shell/.env at submit time.\n"
510
+ "# The GLM 5.2 teacher and key are platform-managed nothing to set up or export.\n"
514
511
  "[environment]\n"
515
- 'id = ""\n'
516
- 'secrets = ["FIREWORKS_API_KEY"]\n\n'
512
+ 'id = ""\n\n'
517
513
  "[train]\n"
518
514
  "steps = 100 # opd is step-driven (like GRPO)\n"
519
515
  "lora_rank = 32\n"
520
- '# teacher_model = "accounts/fireworks/models/glm-5p2" # Fireworks GLM teacher (default)\n'
521
516
  "# GPU and HF artifacts are managed automatically by the platform: the GPU is\n"
522
517
  "# the cheapest fitting managed class, and artifacts live in a private environment-scoped repo.\n"
523
518
  )
@@ -842,13 +837,25 @@ def cmd_cancel(args) -> int:
842
837
  else:
843
838
  print(json.dumps(payload, indent=2))
844
839
  if checkpoints:
845
- top = max(c["step"] for c in checkpoints)
840
+ # Best-effort hint (the cancel already succeeded), so never crash on a malformed checkpoint
841
+ # shape: coerce steps defensively — a dict missing 'step' or carrying a non-int must not raise
842
+ # a traceback here. Only surface the `step-N` deploy example when we recovered a real step.
843
+ steps = []
844
+ for c in checkpoints:
845
+ try:
846
+ steps.append(int(c["step"]))
847
+ except (KeyError, TypeError, ValueError):
848
+ continue
846
849
  # stderr in the plain path so the machine-readable stdout JSON stays untouched.
847
850
  out = sys.stdout if render.styled() else sys.stderr
848
- msg = (
851
+ base = (
849
852
  f"{len(checkpoints)} deployable checkpoint(s) survive this cancel — list with "
850
- f"`flash checkpoints {args.run_id}`, deploy one with "
851
- f"`flash deploy {args.run_id}/step-{top}`."
853
+ f"`flash checkpoints {args.run_id}`"
854
+ )
855
+ msg = (
856
+ f"{base}, deploy one with `flash deploy {args.run_id}/step-{max(steps)}`."
857
+ if steps
858
+ else f"{base}."
852
859
  )
853
860
  print(render.note(msg) if render.styled() else msg, file=out)
854
861
  return 0
@@ -377,20 +377,28 @@ SFT is **epoch-driven** (`epochs`); GRPO is **step-driven** (`steps`).
377
377
  ## On-policy distillation (`algorithm = "opd"`)
378
378
 
379
379
  Pick distillation when a much stronger **teacher** model can grade your student's work
380
- token-by-token. The student samples on-policy (like GRPO), a Fireworks-hosted GLM teacher scores
380
+ token-by-token. The student samples on-policy (like GRPO), the managed GLM 5.2 teacher scores
381
381
  each of *its own* completions, and a dense per-token loss teaches the student to match the teacher —
382
382
  far more sample-efficient than reward-based RL and with no reward to design. It is **step-driven**
383
383
  (`steps`) and produces a LoRA served exactly like SFT.
384
384
 
385
- - **Provide the teacher key.** Distillation needs `FIREWORKS_API_KEY` at submit time. List it under
386
- `[environment] secrets = ["FIREWORKS_API_KEY"]` and export it in your shell / local `.env`; the
387
- value travels out-of-band and is never stored in the spec or needed at serving time.
385
+ - **No teacher key or model override to set up.** The GLM 5.2 teacher and its Fireworks key are platform-managed: the
386
+ service supplies its own key to every opd run, so there is nothing to export or declare — an opd
387
+ run submits like any other. Bring-your-own teacher models and keys are not supported; a
388
+ `FIREWORKS_API_KEY` in your shell is simply ignored. The key is never stored in the spec or
389
+ needed at serving time.
388
390
  - **The student (Qwen / MiniCPM / Kimi) and the teacher (GLM) have different tokenizers.** Flash
389
391
  bridges the vocabulary mismatch with **groupwise reverse-KL** (the collinear-ai *spider* / Tinker
390
392
  method): it aligns the two tokenizations by shared decoded-text spans and applies per-span reverse
391
393
  KL using only realized-token logprobs — no vocabulary projection, so it covers every token exactly
392
394
  and works for any student tokenizer. When the tokenizers happen to agree it reduces to plain
393
395
  per-token reverse KL (Thinking Machines, *On-Policy Distillation*). Nothing to configure.
396
+ - **Works for multi-turn envs too.** Against an `EnvironmentMultiTurn`, opd rolls out each episode
397
+ (driving `step_episode` / observations just like GRPO) and distils EVERY assistant turn against the
398
+ teacher, each conditioned on the transcript up to that turn — the episode's total reverse-KL over
399
+ the student's generated tokens is the sum of its per-turn reverse-KLs. Env/observation tokens are
400
+ never distilled (they're context, not the student's output). Set `[train] max_length` to bound the
401
+ transcript; the teacher must cover it (GLM-5.2's context far exceeds the default budget).
394
402
  - **Judge it like SFT.** Distillation logs a falling per-token loss; a low loss alone is not proof.
395
403
  Keep a held-out split, `flash deploy` the adapter, and score it — confirm the student actually
396
404
  moved toward the teacher's behavior, not just its surface tokens.
@@ -402,12 +410,10 @@ algorithm = "opd"
402
410
 
403
411
  [environment]
404
412
  id = "your-org/my-env"
405
- secrets = ["FIREWORKS_API_KEY"]
406
413
 
407
414
  [train]
408
415
  steps = 100
409
416
  lora_rank = 32
410
- # teacher_model = "accounts/fireworks/models/glm-5p2" # Fireworks GLM teacher (default)
411
417
  # kl_penalty_coef = 1.0 # reverse-KL scale
412
418
  ```
413
419
 
@@ -99,7 +99,7 @@ def seconds_per_step(config: RunConfig, gpu: str) -> float:
99
99
  peak = gpu_tflops(gpu) * 1e12 # FLOP/s
100
100
 
101
101
  if n.is_opd:
102
- # OPD step = on-policy student rollout (like GRPO) + remote teacher scoring (serial
102
+ # OPD step = on-policy student rollout (like GRPO) + remote teacher scoring (CONCURRENT
103
103
  # Fireworks round-trips, replaces reward grading) + policy update (fwd+bwd only, NO local
104
104
  # reference forward — the teacher is the API). Bill local compute on the FULL prompt+completion
105
105
  # sequence (see _opd_step_shape), not completion-only, or long-prompt opd is underquoted.
@@ -107,10 +107,12 @@ def seconds_per_step(config: RunConfig, gpu: str) -> float:
107
107
  gen_s = (GRPO_GEN_FLOPS_PER_TOKEN_PER_PARAM * params * seq_tokens) / (peak * MFU_DECODE)
108
108
  update_s = (OPD_UPDATE_FLOPS_PER_TOKEN_PER_PARAM * params * seq_tokens) / (peak * MFU_TRAIN)
109
109
  teacher_lat = teacher_seconds_per_completion()
110
- # run_opd scores each completion serially (nested prompt/group loops each await
111
- # teacher.score before the next), so the wall cost is the full serial sum — do NOT divide
112
- # by a concurrency factor or the quote materially understates teacher-bound steps.
113
- teacher_s = completions * teacher_lat
110
+ # run_opd's primary path scores a step's completions CONCURRENTLY over Fireworks with a fan-out
111
+ # cap of the step's OWN completion count (prompts_per_step * group_size, opd.py Phase 2), so
112
+ # every completion in a step is scored in ONE parallel wave — the teacher wall is a single
113
+ # latency, NOT the full serial sum (that describes only the CPU-test fallback that can't
114
+ # batch-generate). The teacher endpoint's rate limit is the real ceiling on this fan-out.
115
+ teacher_s = teacher_lat if completions else 0.0
114
116
  return gen_s + teacher_s + update_s
115
117
 
116
118
  if not n.is_grpo:
@@ -255,10 +257,10 @@ def estimate_cost(config: RunConfig, *, wall_cap_s: float = DEFAULT_WALL_CAP_S)
255
257
  train_seconds=train,
256
258
  wall_clock_seconds=wall,
257
259
  wall_capped=wall_capped,
258
- # GPU (platform-billed) time only. OPD's teacher tokens are billed by Fireworks DIRECTLY to
259
- # the user's FIREWORKS_API_KEY (a user-supplied [environment].secret), so folding
260
- # teacher_api_usd into the charged total would bill it a second time keep it itemized as a
261
- # diagnostic instead (codex[bot]).
260
+ # GPU (platform-billed) time only. OPD's teacher tokens are billed by Fireworks to the
261
+ # platform-managed teacher key never through this GPU charge — so folding teacher_api_usd
262
+ # into the charged total would bill it a second time. Keep it itemized as a diagnostic
263
+ # instead (codex[bot]).
262
264
  total_usd=train / 3600.0 * hourly,
263
265
  teacher_api_usd=teacher_api_usd,
264
266
  notes=_notes(config, raw_train, wall_capped, cap_s),
@@ -166,10 +166,9 @@ def reward_seconds_per_completion(override: float | None = None) -> float:
166
166
  # NO FIREWORKS_API_KEY. Source: https://fireworks.ai/models/fireworks/glm-5p2 lists
167
167
  # $1.40 / $0.14 / $4.40 (input / cached input / output) per 1M. opd echo-scores completions
168
168
  # (max_tokens=0), so only INPUT tokens are billed (the teacher never generates) — but the table keeps
169
- # both so a mispriced entry is obvious. glm-5p1 shares the GLM-5 serverless rate.
169
+ # both so a mispriced entry is obvious.
170
170
  TEACHER_USD_PER_1M: dict[str, tuple[float, float]] = {
171
171
  "accounts/fireworks/models/glm-5p2": (1.40, 4.40),
172
- "accounts/fireworks/models/glm-5p1": (1.40, 4.40),
173
172
  }
174
173
  # Fireworks echo-scoring round-trip per completion (wall time, concurrency-bound like reward grading).
175
174
  AVG_TEACHER_SECONDS_PER_COMPLETION = 2.0
@@ -178,11 +177,9 @@ AVG_TEACHER_SECONDS_PER_COMPLETION = 2.0
178
177
  def teacher_price_per_1m(teacher_model: str) -> tuple[float, float]:
179
178
  """(input, output) $/1M tokens for a teacher model.
180
179
 
181
- The empty-string sentinel (an opd run that OMITS [train] teacher_model) resolves to the recipe's
182
- default teacher (``RECIPE.opd.teacher_model``) the SINGLE source of "who is the default
183
- teacher" — so the quote uses that model's real rate with no forked default-price constant. An
184
- unknown non-default teacher falls back to the default teacher's rate (spec-parse already rejects
185
- an unpriced teacher_model, so this is only a defensive backstop)."""
180
+ OPD uses the recipe's fixed GLM 5.2 teacher. ``teacher_model`` is accepted only for internal
181
+ diagnostics/tests; user configs cannot override it. Unknown values fall back defensively to
182
+ the fixed teacher rate."""
186
183
  from flash.engine.recipe import RECIPE
187
184
 
188
185
  default = TEACHER_USD_PER_1M[RECIPE.opd.teacher_model]
@@ -110,9 +110,6 @@ def runconfig_from_spec(spec) -> RunConfig:
110
110
  provider="auto",
111
111
  max_wall_seconds=g.max_wall_seconds,
112
112
  environment=spec.environment.id or None,
113
- # "" is the unset sentinel (RunConfig.teacher_model); facts.teacher_price_per_1m resolves it
114
- # to the recipe default. Non-opd carries no teacher.
115
- teacher_model=t.teacher_model if spec.algorithm == "opd" else "",
116
113
  )
117
114
 
118
115
 
@@ -27,9 +27,8 @@ class RunConfig:
27
27
  thinking: bool = False
28
28
  # GRPO only: seconds to score one completion. None -> the single average grader latency.
29
29
  reward_seconds_per_completion: float | None = None
30
- # OPD only: the Fireworks teacher id, so the teacher-token quote uses the right per-model rate.
31
- # "" (the unset sentinel, matching TrainSpec.teacher_model) resolves to the recipe's default
32
- # teacher (RECIPE.opd.teacher_model) at the pricing boundary (facts.teacher_price_per_1m).
30
+ # OPD only: internal Fireworks teacher id for teacher-token diagnostics. User configs cannot
31
+ # override this; an empty value resolves to the recipe's fixed GLM 5.2 teacher.
33
32
  teacher_model: str = ""
34
33
 
35
34
  max_wall_seconds: int | None = None # wall cap (spec gpu.max_wall_seconds); None = 24h
@@ -150,8 +149,9 @@ class CostEstimate:
150
149
  wall_capped: bool
151
150
  total_usd: float
152
151
  # opd only: external Fireworks GLM teacher token spend (0.0 for sft/grpo). Billed by Fireworks
153
- # DIRECTLY to the user's FIREWORKS_API_KEY, so it is NOT part of total_usd shown as its own
154
- # itemized diagnostic line only.
152
+ # to the platform-managed teacher key (users don't supply one), tracked separately from the
153
+ # platform-billed GPU charge — so it is NOT part of total_usd; shown as its own itemized
154
+ # diagnostic line only.
155
155
  teacher_api_usd: float = 0.0
156
156
  notes: tuple[str, ...] = ()
157
157
 
@@ -181,8 +181,8 @@ class CostEstimate:
181
181
  ]
182
182
  if self.teacher_api_usd > 0:
183
183
  lines.append(
184
- f"Teacher API: ${self.teacher_api_usd:.2f} (Fireworks GLM token spend on your "
185
- "FIREWORKS_API_KEYbilled by Fireworks, NOT included in TOTAL)"
184
+ f"Teacher API: ${self.teacher_api_usd:.2f} (Fireworks GLM token spend on the "
185
+ "platform-managed teacher key tracked separately, NOT included in TOTAL)"
186
186
  )
187
187
  lines.append(f"TOTAL : ${self.total_usd:.2f}")
188
188
  if self.notes:
@@ -27,6 +27,26 @@ class RolloutResult(TypedDict):
27
27
  reward: float
28
28
 
29
29
 
30
+ class TurnRecord(TypedDict):
31
+ """One assistant turn of a multi-turn episode, as :func:`rollout_one_records` emits it.
32
+
33
+ ``prefix_ids`` are the student token ids of the whole transcript BEFORE this turn (initial prompt
34
+ + every prior assistant turn + every inter-turn env "glue"), i.e. the exact on-policy context the
35
+ student sampled this turn's completion after. ``context_messages`` is the parallel message list at
36
+ the same point (prompt + prior turns + prior env replies), for callers that render a separate
37
+ teacher/scoring prompt from it. ``gen`` is whatever the injected ``generate`` callable returned for
38
+ this turn (opaque to the driver beyond the four attributes it reads: ``completion_ids``,
39
+ ``completion_text``, ``truncated``, ``skip``) — e.g. OPD hands back its ``_GenResult`` so the
40
+ per-turn record feeds straight into the existing single-turn scoring/loss path. Distilling each
41
+ turn against ``prefix_ids`` is the multi-turn on-policy-distillation objective: the episode's total
42
+ reverse-KL over student-generated tokens is the sum of per-turn reverse-KLs, each conditioned on the
43
+ transcript so far."""
44
+
45
+ prefix_ids: list[int]
46
+ gen: object
47
+ context_messages: list[dict]
48
+
49
+
30
50
  _ROLLOUT_FIELDS: tuple[str, ...] = (
31
51
  "prompt_ids",
32
52
  "completion_ids",
@@ -99,6 +119,20 @@ def index_collisions(rows: list[dict], prompt_of: Callable[[dict], object]) -> i
99
119
  return len(rows) - len({_prompt_key(prompt_of(r)) for r in rows})
100
120
 
101
121
 
122
+ def _dedup_seam_terminator(prev_completion_ids: list[int], glue: list[int]) -> list[int]:
123
+ """Collapse a duplicate turn terminator at the turn/env seam.
124
+
125
+ ``env_glue`` leads with the assistant turn's terminator (e.g. ``<|im_end|>``), which a
126
+ naturally-stopped assistant turn also keeps as its final token — so the raw stream would carry two.
127
+ When the previous turn's last token equals ``glue[0]`` drop the glue copy, keeping the assistant's
128
+ own token (real logprob / model-generated) so the stream has exactly one terminator per turn,
129
+ matching the chat template + SFT transcripts. Shared by :func:`rollout_one`,
130
+ :func:`_advance_after_turn`, and :func:`rollout_one_records` so the three paths can't drift."""
131
+ if glue and prev_completion_ids and prev_completion_ids[-1] == glue[0]:
132
+ return glue[1:]
133
+ return glue
134
+
135
+
102
136
  def rollout_one(
103
137
  *,
104
138
  example: dict,
@@ -152,13 +186,7 @@ def rollout_one(
152
186
  if active_env.rollout_done(state, max_turns):
153
187
  break
154
188
 
155
- glue = env_glue(env_msgs)
156
- # Collapse a duplicate turn terminator at the seam: env_glue leads with the assistant turn's
157
- # terminator (e.g. <|im_end|>), which vLLM also keeps as the last token of a naturally-stopped
158
- # turn — keep the assistant's own (env_mask=1, real logprob) over the env copy. See
159
- # _advance_after_turn (the rollout_async twin) for the same fix.
160
- if glue and completion_ids and completion_ids[-1] == glue[0]:
161
- glue = glue[1:]
189
+ glue = _dedup_seam_terminator(completion_ids, env_glue(env_msgs))
162
190
  if token_budget is not None and len(completion_ids) + len(glue) > token_budget:
163
191
  break
164
192
  completion_ids.extend(glue)
@@ -176,6 +204,102 @@ def rollout_one(
176
204
  }
177
205
 
178
206
 
207
+ def rollout_one_records(
208
+ *,
209
+ example: dict,
210
+ active_env,
211
+ render: Callable[[list, bool], list[int]],
212
+ generate: Callable[[list, int], object],
213
+ env_glue: Callable[[list], list[int]],
214
+ max_turns: int,
215
+ per_turn_max_tokens: int,
216
+ engine_max_len: int | None = None,
217
+ on_turn_generated: Callable[[], None] | None = None,
218
+ ) -> list[TurnRecord]:
219
+ """Drive one multi-turn episode and return a per-turn :class:`TurnRecord` list (NOT a flat masked
220
+ sequence like :func:`rollout_one`). Used by OPD, which distils each assistant turn as an independent
221
+ single-turn sample conditioned on the transcript so far.
222
+
223
+ The env turn loop is identical to :func:`rollout_one` (``new_rollout_state`` → generate →
224
+ ``record_model_turn`` → ``rollout_done`` → ``env_reply`` → glue), and the tokenizer-sensitive seam
225
+ dedup / engine-budget accounting reuse the same helpers, so the two paths can't drift. The
226
+ differences are all OPD-shaped:
227
+
228
+ - ``generate(prefix_ids, max_new)`` returns an OBJECT (opaque here) exposing ``completion_ids``
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.
232
+ - Every turn (including a truncated/empty one) is recorded so the caller counts it; a
233
+ truncated/skip turn ENDS the episode (a student that didn't terminate its turn, or emitted an
234
+ empty/invalid completion, can't meaningfully continue — and its bad turn is skipped, not
235
+ distilled, by the caller's ``_resolve_sample``).
236
+ - ``on_turn_generated`` (optional) fires AFTER each turn's generation to refresh the worker stall
237
+ clock and advance the sample counter — a many-turn episode is a long serial stretch of GPU
238
+ generates + teacher-less env steps that would otherwise emit no progress ping.
239
+
240
+ ``prefix_ids`` and ``context_messages`` are snapshotted BEFORE generation, so each record carries
241
+ the exact student-token and message context the teacher must condition on for that turn.
242
+ """
243
+ state = active_env.new_rollout_state(example)
244
+ initial_messages = state.get("prompt") or state.get("messages")
245
+ if not isinstance(initial_messages, list):
246
+ raise KeyError("multi-turn rollout state must include prompt or messages")
247
+ messages = [dict(m) for m in initial_messages]
248
+ prompt_ids = render(messages, True)
249
+ cur_ids = list(prompt_ids) # invariant: cur_ids == prompt_ids + completion tokens so far
250
+ token_budget = (engine_max_len - len(prompt_ids) - 8) if engine_max_len else None
251
+ records: list[TurnRecord] = []
252
+
253
+ turns = 0
254
+ while True:
255
+ completion_so_far = len(cur_ids) - len(prompt_ids)
256
+ max_new = per_turn_max_tokens
257
+ if token_budget is not None:
258
+ remaining = token_budget - completion_so_far
259
+ if remaining <= 0:
260
+ break
261
+ max_new = min(max_new, remaining)
262
+ # Snapshot the student-token prefix and message context BEFORE generation: this is what the
263
+ # teacher must condition on to score THIS turn (the transcript up to, but excluding, the turn).
264
+ prefix_ids = list(cur_ids)
265
+ context_messages = [dict(m) for m in messages]
266
+ gen = generate(prefix_ids, max(1, max_new))
267
+ if on_turn_generated is not None:
268
+ on_turn_generated()
269
+ records.append(
270
+ {"prefix_ids": prefix_ids, "gen": gen, "context_messages": context_messages}
271
+ )
272
+ text = getattr(gen, "completion_text", "") or ""
273
+ active_env.record_model_turn(state, text)
274
+ messages.append({"role": "assistant", "content": text})
275
+ turns += 1
276
+ # A turn that didn't terminate naturally (truncated) or produced no usable text (skip) ends the
277
+ # episode: it's recorded (counted) but not distilled, and continuing from a broken turn is
278
+ # pointless (the student can't end its turn / said nothing).
279
+ if getattr(gen, "truncated", False) or getattr(gen, "skip", False):
280
+ break
281
+ asst_ids = getattr(gen, "completion_ids", None) or []
282
+ cur_ids.extend(asst_ids)
283
+ completion_so_far = len(cur_ids) - len(prompt_ids)
284
+ if token_budget is not None and completion_so_far >= token_budget:
285
+ break
286
+ if turns >= max_turns or active_env.rollout_done(state, max_turns):
287
+ break
288
+ env_msgs = active_env.env_reply(messages, state)
289
+ if not env_msgs:
290
+ break
291
+ messages.extend(env_msgs)
292
+ # Don't append glue if the env step finished — no next model turn.
293
+ if active_env.rollout_done(state, max_turns):
294
+ break
295
+ glue = _dedup_seam_terminator(asst_ids, env_glue(env_msgs))
296
+ if token_budget is not None and completion_so_far + len(glue) > token_budget:
297
+ break
298
+ cur_ids.extend(glue)
299
+
300
+ return records
301
+
302
+
179
303
  class _RolloutState:
180
304
  """Mutable per-rollout accumulator for :func:`rollout_async` (mirrors :func:`rollout_one` locals)."""
181
305
 
@@ -248,13 +372,7 @@ def _advance_after_turn(
248
372
  if active_env.rollout_done(r.state, max_turns):
249
373
  r.done = True
250
374
  return
251
- glue = env_glue(env_msgs)
252
- # env_glue's text starts at the assistant turn's terminator (e.g. <|im_end|>); vLLM also keeps that
253
- # terminator as the final token of a naturally-stopped turn (it's in asst_ids). Collapse the duplicate
254
- # at the seam so the stream has exactly one terminator per turn (matches the chat template + SFT
255
- # transcripts), keeping the assistant's own token (env_mask=1, real logprob) over the env copy.
256
- if glue and r.completion_ids and r.completion_ids[-1] == glue[0]:
257
- glue = glue[1:]
375
+ glue = _dedup_seam_terminator(r.completion_ids, env_glue(env_msgs))
258
376
  if r.budget is not None and len(r.completion_ids) + len(glue) > r.budget:
259
377
  r.done = True
260
378
  return
@@ -429,6 +547,47 @@ def render_message_ids(tok, messages, add_generation_prompt: bool, *, thinking:
429
547
  return [int(t) for t in tok(text, add_special_tokens=False).input_ids]
430
548
 
431
549
 
550
+ def make_env_glue(tok, *, thinking: bool, cache_size: int = 8192) -> Callable[[list], list[int]]:
551
+ """Build the inter-turn "glue" tokenizer used by both the GRPO rollout (:func:`build_rollout_func`)
552
+ and OPD's per-turn rollout (:func:`rollout_one_records`).
553
+
554
+ Given the env's reply messages for a turn, returns the token ids that sit BETWEEN the assistant
555
+ turn and the next assistant generation prompt (the turn terminator + the env/observation messages +
556
+ the next assistant header). Computed with the probe trick — render ``[{assistant: PROBE}, *env]``
557
+ with ``add_generation_prompt=True`` and take everything after the probe — so the history is never
558
+ re-rendered (Qwen3's template doesn't round-trip a re-rendered transcript). Results are LRU-cached
559
+ by the env messages. Raises ``ValueError`` if the model's chat template doesn't insert assistant
560
+ content verbatim (token-aligned multi-turn is then unsupported for that model)."""
561
+ cache = _LRUCache(cache_size)
562
+ probe = "flash-env-glue-probe"
563
+
564
+ def env_glue(env_messages: list) -> list[int]:
565
+ cache_key = json.dumps(env_messages, sort_keys=True, default=str)
566
+ cached = cache.get(cache_key)
567
+ if cached is not None:
568
+ return cached
569
+ text = tok.apply_chat_template(
570
+ [{"role": "assistant", "content": probe}, *env_messages],
571
+ add_generation_prompt=True,
572
+ tokenize=False,
573
+ enable_thinking=thinking,
574
+ )
575
+ first = text.find(probe)
576
+ if first == -1 or text.find(probe, first + len(probe)) != -1:
577
+ raise ValueError(
578
+ "multi-turn env_glue could not uniquely locate its probe in the rendered chat "
579
+ "template; this model's template does not insert assistant content verbatim, so "
580
+ "token-aligned multi-turn rollout is unsupported for it (use a single-turn/tool "
581
+ "env or a different model)."
582
+ )
583
+ glue_text = text[first + len(probe) :]
584
+ glue = [int(t) for t in tok(glue_text, add_special_tokens=False).input_ids]
585
+ cache.put(cache_key, glue)
586
+ return glue
587
+
588
+ return env_glue
589
+
590
+
432
591
  def _engine_vocab_size(engine) -> int | None:
433
592
  """Best-effort vocab size from the colocate vLLM engine, or None. Never raises."""
434
593
  try:
@@ -482,34 +641,7 @@ def build_rollout_func(
482
641
  _render_cache.put(cache_key, ids)
483
642
  return ids
484
643
 
485
- _glue_cache = _LRUCache(8192)
486
-
487
- def env_glue(env_messages: list) -> list[int]:
488
- cache_key = json.dumps(env_messages, sort_keys=True, default=str)
489
- cached = _glue_cache.get(cache_key)
490
- if cached is not None:
491
- return cached
492
- # Render a probe assistant turn + env messages, then take everything after the probe
493
- # to get the inter-turn glue without re-rendering history (Qwen3 doesn't round-trip).
494
- probe = "flash-env-glue-probe"
495
- text = tok.apply_chat_template(
496
- [{"role": "assistant", "content": probe}, *env_messages],
497
- add_generation_prompt=True,
498
- tokenize=False,
499
- enable_thinking=thinking,
500
- )
501
- first = text.find(probe)
502
- if first == -1 or text.find(probe, first + len(probe)) != -1:
503
- raise ValueError(
504
- "multi-turn env_glue could not uniquely locate its probe in the rendered chat "
505
- "template; this model's template does not insert assistant content verbatim, so "
506
- "token-aligned multi-turn rollout is unsupported for it (use a single-turn/tool "
507
- "env or a different model)."
508
- )
509
- glue_text = text[first + len(probe) :]
510
- glue = [int(t) for t in tok(glue_text, add_special_tokens=False).input_ids]
511
- _glue_cache.put(cache_key, glue)
512
- return glue
644
+ env_glue = make_env_glue(tok, thinking=thinking)
513
645
 
514
646
  def rollout_func(prompts, trainer):
515
647
  engine = trainer.vllm_generation.llm
@@ -50,9 +50,8 @@ class OPDConfig:
50
50
  """On-policy distillation: student samples, a remote teacher scores its tokens, and a groupwise
51
51
  reverse-KL loss (the collinear-ai spider / Tinker cross-tokenizer method) trains the student."""
52
52
 
53
- # Fireworks-hosted teacher reached over the OpenAI-compatible API; overridable via
54
- # [train].teacher_model. GLM-5.2 is a strong reasoning model whose per-token logprobs
55
- # supervise the (much smaller) student.
53
+ # Fixed Fireworks-hosted GLM 5.2 teacher reached over the OpenAI-compatible API. This is
54
+ # platform-managed and intentionally not configurable from [train].
56
55
  teacher_model: str = "accounts/fireworks/models/glm-5p2"
57
56
  teacher_base_url: str = "https://api.fireworks.ai/inference/v1"
58
57
  # OPD is step-driven like GRPO (on-policy sampling), not epoch-driven like SFT.
@@ -35,6 +35,7 @@ from flash.engine.worker.decoding import (
35
35
  from flash.engine.worker.finalize import write_train_meta
36
36
  from flash.engine.worker.gpu_setup import (
37
37
  finalize_alloc_conf_for_sleep,
38
+ force_vit_sdpa_on_blackwell,
38
39
  force_vllm_backend_for_sm120,
39
40
  patch_trl_colocate_llm_kwargs,
40
41
  )
@@ -54,6 +55,8 @@ from flash.engine.worker.heartbeat import (
54
55
  _HB_TERMINAL_ONLY_INTERVAL_S,
55
56
  _HB_TERMINAL_STAGES,
56
57
  _HB_THROTTLED_STAGES,
58
+ _HB_TIGHT_LIVENESS_STAGES,
59
+ _HB_UPLOAD_LIVENESS_STAGES,
57
60
  _HB_UPLOAD_LOCK,
58
61
  _SFT_HEARTBEAT_INTERVAL_S,
59
62
  _STEP_GPU_DIAG_INTERVAL_S,
@@ -371,6 +374,8 @@ __all__ = [
371
374
  "_HB_TERMINAL_ONLY_INTERVAL_S",
372
375
  "_HB_TERMINAL_STAGES",
373
376
  "_HB_THROTTLED_STAGES",
377
+ "_HB_TIGHT_LIVENESS_STAGES",
378
+ "_HB_UPLOAD_LIVENESS_STAGES",
374
379
  "_HB_UPLOAD_LOCK",
375
380
  "_SFT_HEARTBEAT_INTERVAL_S",
376
381
  "_STEP_GPU_DIAG_INTERVAL_S",
@@ -412,6 +417,7 @@ __all__ = [
412
417
  "disable_liger_grpo_torch_compile",
413
418
  "error_artifact_name",
414
419
  "finalize_alloc_conf_for_sleep",
420
+ "force_vit_sdpa_on_blackwell",
415
421
  "force_vllm_backend_for_sm120",
416
422
  "free_gpu",
417
423
  "fused_optim_name",