freesolo-flash-dev 1.0.2__tar.gz → 1.0.3__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 (345) hide show
  1. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/.github/workflows/bake-kernel-cache.yml +5 -6
  2. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/PKG-INFO +1 -1
  3. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/catalog.py +54 -8
  4. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/cli/commands.py +15 -6
  5. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/cli/render.py +4 -1
  6. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/cli/training_doc.py +6 -4
  7. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/engine/multiturn_rollout.py +130 -22
  8. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/engine/recipe.py +7 -0
  9. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/engine/worker/grpo.py +8 -2
  10. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/engine/worker/opd.py +473 -75
  11. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/engine/worker/opd_vllm.py +43 -7
  12. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/engine/worker/packing.py +42 -11
  13. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/engine/worker/perf/__init__.py +4 -0
  14. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/engine/worker/perf/memory.py +49 -0
  15. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/engine/worker/rl.py +96 -16
  16. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/engine/worker/sft.py +82 -18
  17. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/engine/worker/teacher.py +193 -0
  18. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/envs/adapter.py +3 -0
  19. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/envs/loader.py +1 -0
  20. freesolo_flash_dev-1.0.3/flash/multimodal.py +1004 -0
  21. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/providers/_instance_poll.py +29 -17
  22. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/providers/_worker.py +1 -1
  23. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/runner/__init__.py +157 -13
  24. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/runner/deploy.py +418 -111
  25. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/runner/lifecycle.py +193 -21
  26. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/serve/pricing.py +15 -6
  27. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/server/_runtime.py +191 -47
  28. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/pyproject.toml +2 -2
  29. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_allocator.py +10 -0
  30. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_catalog_consistency.py +22 -2
  31. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_cli_commands.py +1 -0
  32. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_cli_commands_coverage.py +3 -0
  33. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_cli_render_coverage.py +8 -0
  34. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_cost_models.py +2 -2
  35. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_disk_gb.py +5 -9
  36. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_env_push.py +19 -0
  37. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_flash_mvp.py +5 -1
  38. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_gpus.py +2 -0
  39. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_grpo_params.py +60 -0
  40. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_jobs.py +492 -44
  41. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_kernel_fingerprint.py +12 -3
  42. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_lora_rank_preflight.py +15 -5
  43. freesolo_flash_dev-1.0.3/tests/test_multimodal_input_grads.py +75 -0
  44. freesolo_flash_dev-1.0.3/tests/test_multimodal_training.py +969 -0
  45. freesolo_flash_dev-1.0.3/tests/test_multiturn_image_rollout.py +291 -0
  46. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_multiturn_rollout.py +1 -1
  47. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_multiturn_rollout_coverage.py +1 -1
  48. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_multiturn_rollout_request_policy.py +4 -4
  49. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_opd.py +766 -15
  50. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_opd_vllm.py +46 -1
  51. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_packing.py +66 -0
  52. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_provider_routing.py +17 -2
  53. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_resolve_params_b.py +1 -0
  54. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_resume_on_retry.py +63 -24
  55. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_runmgmt.py +422 -24
  56. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_serve.py +28 -0
  57. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_server_api.py +3 -4
  58. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_serving_context_preflight.py +1 -0
  59. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_serving_contract.py +3 -2
  60. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_sft_gc_off.py +7 -1
  61. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_train_context_preflight.py +22 -0
  62. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_verifiers.py +22 -2
  63. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_weight_cache.py +14 -5
  64. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_worker_image.py +17 -14
  65. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/uv.lock +1 -1
  66. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/.claude/skills/verify/SKILL.md +0 -0
  67. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/.dockerignore +0 -0
  68. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/.env.example +0 -0
  69. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/.github/workflows/auto-rebake.yml +0 -0
  70. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/.github/workflows/ci.yml +0 -0
  71. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/.github/workflows/main-source-guard.yml +0 -0
  72. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/.github/workflows/notify-tests-repo.yml +0 -0
  73. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/.github/workflows/publish-dev.yml +0 -0
  74. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/.github/workflows/publish-image.yml +0 -0
  75. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/.github/workflows/publish.yml +0 -0
  76. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/.github/workflows/version-parity.yml +0 -0
  77. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/.github/workflows/worker-image.yml +0 -0
  78. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/.gitignore +0 -0
  79. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/Dockerfile +0 -0
  80. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/Dockerfile.worker +0 -0
  81. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/LICENSE +0 -0
  82. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/README.md +0 -0
  83. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/build/kernel_cache/.gitignore +0 -0
  84. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/build/kernel_cache/.keep +0 -0
  85. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/docker/Dockerfile.kernelcache +0 -0
  86. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/docker/Dockerfile.kernelcache.relayer +0 -0
  87. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/docker/bake_kernel_cache.py +0 -0
  88. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/docker/bake_pod_entry.py +0 -0
  89. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/docker/kernel_fingerprint.py +0 -0
  90. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/docker/make_rp_handler.py +0 -0
  91. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/__init__.py +0 -0
  92. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/_channel.py +0 -0
  93. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/_fileio.py +0 -0
  94. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/_logging.py +0 -0
  95. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/_update_check.py +0 -0
  96. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/adapter_artifacts.py +0 -0
  97. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/cli/__init__.py +0 -0
  98. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/cli/__main__.py +0 -0
  99. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/cli/_tty.py +0 -0
  100. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/cli/env_setup.py +0 -0
  101. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/cli/envpush.py +0 -0
  102. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/client/__init__.py +0 -0
  103. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/client/config.py +0 -0
  104. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/client/http.py +0 -0
  105. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/client/runtime_secrets.py +0 -0
  106. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/client/specs.py +0 -0
  107. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/cost/__init__.py +0 -0
  108. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/cost/analytical.py +0 -0
  109. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/cost/facts.py +0 -0
  110. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/cost/spec.py +0 -0
  111. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/cost/types.py +0 -0
  112. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/diagnostics.py +0 -0
  113. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/engine/__init__.py +0 -0
  114. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/engine/accounting.py +0 -0
  115. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/engine/chalk_kernels.py +0 -0
  116. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/engine/steps.py +0 -0
  117. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/engine/structured_outputs.py +0 -0
  118. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/engine/vram.py +0 -0
  119. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/engine/worker/__init__.py +0 -0
  120. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/engine/worker/__main__.py +0 -0
  121. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/engine/worker/_pkg.py +0 -0
  122. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/engine/worker/adapter.py +0 -0
  123. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/engine/worker/decoding.py +0 -0
  124. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/engine/worker/finalize.py +0 -0
  125. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/engine/worker/gpu_setup.py +0 -0
  126. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/engine/worker/heartbeat.py +0 -0
  127. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/engine/worker/hf.py +0 -0
  128. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/engine/worker/kernel_warmup.py +0 -0
  129. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/engine/worker/lora.py +0 -0
  130. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/engine/worker/opd_gkd.py +0 -0
  131. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/engine/worker/perf/attn.py +0 -0
  132. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/engine/worker/perf/diagnostics.py +0 -0
  133. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/engine/worker/perf/lifecycle.py +0 -0
  134. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/engine/worker/perf/liger.py +0 -0
  135. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/engine/worker/perf/loraplus.py +0 -0
  136. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/engine/worker/rng.py +0 -0
  137. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/engine/worker/tokenizer_align.py +0 -0
  138. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/engine/worker/wandb_log.py +0 -0
  139. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/engine/worker_entrypoint.py +0 -0
  140. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/envs/__init__.py +0 -0
  141. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/envs/archive.py +0 -0
  142. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/envs/archive_policy.py +0 -0
  143. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/envs/base.py +0 -0
  144. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/envs/pull.py +0 -0
  145. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/envs/registry.py +0 -0
  146. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/lora_rank.py +0 -0
  147. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/opd_retry_contract.py +0 -0
  148. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/providers/__init__.py +0 -0
  149. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/providers/_auth.py +0 -0
  150. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/providers/_deadline.py +0 -0
  151. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/providers/_hf_artifacts.py +0 -0
  152. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/providers/_hf_retry.py +0 -0
  153. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/providers/_http.py +0 -0
  154. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/providers/_instance.py +0 -0
  155. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/providers/_instance_bootstrap.py +0 -0
  156. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/providers/_instance_provider.py +0 -0
  157. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/providers/_poll.py +0 -0
  158. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/providers/allocator.py +0 -0
  159. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/providers/base.py +0 -0
  160. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/providers/lambdalabs/__init__.py +0 -0
  161. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/providers/lambdalabs/api.py +0 -0
  162. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/providers/lambdalabs/auth.py +0 -0
  163. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/providers/lambdalabs/gpus.py +0 -0
  164. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/providers/lambdalabs/jobs/__init__.py +0 -0
  165. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/providers/lambdalabs/jobs/builders.py +0 -0
  166. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/providers/lambdalabs/preflight.py +0 -0
  167. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/providers/lambdalabs/pricing.py +0 -0
  168. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/providers/preflight.py +0 -0
  169. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/providers/realized.py +0 -0
  170. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/providers/runpod/__init__.py +0 -0
  171. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/providers/runpod/api.py +0 -0
  172. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/providers/runpod/auth.py +0 -0
  173. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/providers/runpod/cost.py +0 -0
  174. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/providers/runpod/gpus.py +0 -0
  175. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/providers/runpod/jobs.py +0 -0
  176. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/providers/runpod/keys.py +0 -0
  177. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/providers/runpod/preflight.py +0 -0
  178. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/providers/runpod/preload.py +0 -0
  179. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/providers/runpod/pricing.py +0 -0
  180. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/providers/runpod/slots.py +0 -0
  181. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/providers/runpod/train/__init__.py +0 -0
  182. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/providers/runpod/train/endpoints.py +0 -0
  183. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/providers/vast/__init__.py +0 -0
  184. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/providers/vast/api.py +0 -0
  185. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/providers/vast/auth.py +0 -0
  186. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/providers/vast/jobs/__init__.py +0 -0
  187. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/providers/vast/jobs/builders.py +0 -0
  188. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/providers/vast/preflight.py +0 -0
  189. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/providers/vast/pricing.py +0 -0
  190. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/py.typed +0 -0
  191. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/runner/checkpoints.py +0 -0
  192. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/runner/verified_revisions.py +0 -0
  193. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/schema/__init__.py +0 -0
  194. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/schema/fields.py +0 -0
  195. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/serve/__init__.py +0 -0
  196. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/serve/deploy.py +0 -0
  197. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/serve/export.py +0 -0
  198. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/serve/urls.py +0 -0
  199. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/server/__init__.py +0 -0
  200. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/server/__main__.py +0 -0
  201. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/server/_deps.py +0 -0
  202. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/server/_internal_client.py +0 -0
  203. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/server/_locks.py +0 -0
  204. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/server/app.py +0 -0
  205. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/server/auth.py +0 -0
  206. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/server/billing.py +0 -0
  207. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/server/billing_retry.py +0 -0
  208. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/server/checkpoints.py +0 -0
  209. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/server/db.py +0 -0
  210. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/server/environment_registry.py +0 -0
  211. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/server/envs.py +0 -0
  212. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/server/reconcile.py +0 -0
  213. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/server/repo_cleanup.py +0 -0
  214. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/server/routes/__init__.py +0 -0
  215. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/server/routes/envs.py +0 -0
  216. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/server/routes/meta.py +0 -0
  217. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/server/routes/runs.py +0 -0
  218. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/server/routes/serving.py +0 -0
  219. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/server/run_registry.py +0 -0
  220. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/flash/spec.py +0 -0
  221. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/infisical-entrypoint.sh +0 -0
  222. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/scripts/build_dev_dist.py +0 -0
  223. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/__init__.py +0 -0
  224. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/_helpers/__init__.py +0 -0
  225. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/_helpers/runner.py +0 -0
  226. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/_helpers/specs.py +0 -0
  227. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/_helpers/vast.py +0 -0
  228. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/conftest.py +0 -0
  229. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/fixtures/math_eval.jsonl +0 -0
  230. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/fixtures/math_train.jsonl +0 -0
  231. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/live/__init__.py +0 -0
  232. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/live/conftest.py +0 -0
  233. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/live/test_lambda_live.py +0 -0
  234. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/live/test_runpod_live.py +0 -0
  235. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/live/test_vast_live.py +0 -0
  236. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_accounting_coverage.py +0 -0
  237. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_agent_flash_cli_contract.py +0 -0
  238. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_algorithms.py +0 -0
  239. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_b200_rollout_opt.py +0 -0
  240. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_backend_jobspec_contract.py +0 -0
  241. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_base_model_provenance.py +0 -0
  242. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_billing_retry.py +0 -0
  243. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_cancel_remote.py +0 -0
  244. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_chalk_kernels.py +0 -0
  245. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_charge_pricing.py +0 -0
  246. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_checkpoints.py +0 -0
  247. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_cli_errors.py +0 -0
  248. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_cli_estimate.py +0 -0
  249. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_cli_help.py +0 -0
  250. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_cli_main_coverage.py +0 -0
  251. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_cli_managed.py +0 -0
  252. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_cli_render_theme.py +0 -0
  253. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_client.py +0 -0
  254. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_client_server_integration.py +0 -0
  255. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_config_overrides.py +0 -0
  256. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_controlled_experiment_repairs.py +0 -0
  257. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_cost_analytical.py +0 -0
  258. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_cost_equation.py +0 -0
  259. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_cost_estimate.py +0 -0
  260. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_cost_hardware.py +0 -0
  261. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_cost_rewards.py +0 -0
  262. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_dev_channel.py +0 -0
  263. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_endpoint_name.py +0 -0
  264. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_env_cache_evict.py +0 -0
  265. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_env_delete.py +0 -0
  266. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_env_download.py +0 -0
  267. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_env_download_api.py +0 -0
  268. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_env_no_install.py +0 -0
  269. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_env_publish.py +0 -0
  270. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_env_pull.py +0 -0
  271. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_env_pull_coverage.py +0 -0
  272. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_env_pull_loader_coverage.py +0 -0
  273. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_env_pull_managed_control_plane.py +0 -0
  274. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_env_rate_limit_resolve.py +0 -0
  275. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_envs_coverage.py +0 -0
  276. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_export.py +0 -0
  277. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_flash_worker.py +0 -0
  278. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_flashinfer_cache_dirs.py +0 -0
  279. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_github_urlopen_retry.py +0 -0
  280. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_grpo_mask_aware.py +0 -0
  281. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_grpo_sleep_gate.py +0 -0
  282. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_health_liveness.py +0 -0
  283. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_hf_retry.py +0 -0
  284. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_idle_endpoint_reaper.py +0 -0
  285. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_instance_bootstrap_coverage.py +0 -0
  286. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_internal_client.py +0 -0
  287. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_kernel_cache.py +0 -0
  288. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_kv_util.py +0 -0
  289. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_lambda_api_coverage.py +0 -0
  290. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_lambda_runner.py +0 -0
  291. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_logging.py +0 -0
  292. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_login_perms.py +0 -0
  293. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_lora_rank_coverage.py +0 -0
  294. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_managed_hf_repo.py +0 -0
  295. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_metrics_schema_agent_contract.py +0 -0
  296. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_mig_guard.py +0 -0
  297. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_multiturn_rollout_records.py +0 -0
  298. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_oom_escalate_gpu.py +0 -0
  299. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_opd_full_state_resume.py +0 -0
  300. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_opd_resume_safety.py +0 -0
  301. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_open_model_policy.py +0 -0
  302. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_orchestrator_flash.py +0 -0
  303. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_packing_coverage.py +0 -0
  304. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_poll_helpers.py +0 -0
  305. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_preflight.py +0 -0
  306. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_provider_preflight_coverage.py +0 -0
  307. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_provider_teardown_robustness.py +0 -0
  308. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_providers_symmetry.py +0 -0
  309. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_realized_cost.py +0 -0
  310. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_repo_cleanup.py +0 -0
  311. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_runpod_api_delete.py +0 -0
  312. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_runpod_key_fingerprint.py +0 -0
  313. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_runpod_key_waterfall.py +0 -0
  314. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_runpod_slots.py +0 -0
  315. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_server_app_coverage.py +0 -0
  316. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_server_billing.py +0 -0
  317. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_server_checkpoints_coverage.py +0 -0
  318. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_server_db.py +0 -0
  319. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_server_envs_coverage.py +0 -0
  320. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_server_locks_coverage.py +0 -0
  321. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_serving_pricing_coverage.py +0 -0
  322. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_serving_schema_coverage.py +0 -0
  323. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_sft_example_selection.py +0 -0
  324. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_sft_max_context.py +0 -0
  325. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_spec_and_validation.py +0 -0
  326. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_structured_outputs.py +0 -0
  327. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_thinking_config.py +0 -0
  328. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_training_controls.py +0 -0
  329. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_update_check.py +0 -0
  330. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_vast_api.py +0 -0
  331. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_vast_api_coverage.py +0 -0
  332. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_vast_offers.py +0 -0
  333. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_vast_provider_coverage.py +0 -0
  334. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_vast_runner.py +0 -0
  335. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_verified_revisions.py +0 -0
  336. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_version.py +0 -0
  337. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_vl_warmstart_adapter_keys.py +0 -0
  338. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_wandb_log_coverage.py +0 -0
  339. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_wandb_naming.py +0 -0
  340. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_warmstart_cross_repo.py +0 -0
  341. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_worker_dryrun.py +0 -0
  342. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_worker_hardexit.py +0 -0
  343. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_worker_init_heartbeat.py +0 -0
  344. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_worker_stack.py +0 -0
  345. {freesolo_flash_dev-1.0.2 → freesolo_flash_dev-1.0.3}/tests/test_worker_thinking.py +0 -0
@@ -22,7 +22,7 @@ on:
22
22
  inputs:
23
23
  sms:
24
24
  description: "comma-separated sm list to bake; default is all validated arches"
25
- default: "sm80,sm86,sm89,sm90,sm120"
25
+ default: "sm80,sm86,sm89,sm90,sm120,sm100"
26
26
  required: false
27
27
  # auto-rebake.yml calls this for the EXPENSIVE path (the cache is invalid and must be re-warmed on a
28
28
  # real GPU). It passes the arch subset, the fingerprints to stamp, and a digest-pinned base so the
@@ -105,10 +105,9 @@ jobs:
105
105
  gpu_type_id: "NVIDIA RTX PRO 6000 Blackwell Server Edition",
106
106
  allowed_cuda: "13.0",
107
107
  }
108
- # Datacenter Blackwell (sm100, distinct from the sm120 RTX Pro 6000). B200 has 180 GB and
109
- # good secure-cloud capacity. CUDA-13 host like the other Blackwell parts. NOT in the default
110
- # `sms` list yet (B200 is unvalidated) -- bake it explicitly via `sms=sm100` until a smoke
111
- # passes, then add it to the default.
108
+ # datacenter blackwell (sm100, distinct from the sm120 rtx pro 6000). b200 has 180 gb,
109
+ # good secure-cloud capacity, and is validated. promote its image only after a successful
110
+ # real-b200 bake, then include it in default and automatic rebakes.
112
111
  - {
113
112
  sm: sm100,
114
113
  arch: "10.0",
@@ -120,7 +119,7 @@ jobs:
120
119
  - name: Should bake ${{ matrix.sm }}?
121
120
  id: gate
122
121
  run: |
123
- req="${{ inputs.sms || 'sm80,sm86,sm89,sm90,sm120' }}"
122
+ req="${{ inputs.sms || 'sm80,sm86,sm89,sm90,sm120,sm100' }}"
124
123
  req="${req// /}" # tolerate spaces, e.g. "sm80, sm86"
125
124
  case ",$req," in
126
125
  *,${{ matrix.sm }},*) echo "run=true" >> "$GITHUB_OUTPUT" ;;
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: freesolo-flash-dev
3
- Version: 1.0.2
3
+ Version: 1.0.3
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
@@ -13,6 +13,22 @@ ALGORITHMS = ("sft", "grpo", "opd")
13
13
  # the cost model, VRAM sizing and worker allocator branch on — import ``samples_on_policy`` instead
14
14
  # of hand-rolling the ("grpo", "opd") tuple at each site.
15
15
  _ON_POLICY_ALGORITHMS = frozenset({"grpo", "opd"})
16
+ _IMAGE_TRAINING_MODELS = frozenset(
17
+ {
18
+ "Qwen/Qwen3.5-0.8B",
19
+ "Qwen/Qwen3.5-2B",
20
+ "Qwen/Qwen3.5-4B",
21
+ "Qwen/Qwen3.5-9B",
22
+ "Qwen/Qwen3.6-27B",
23
+ "Qwen/Qwen3.6-35B-A3B",
24
+ }
25
+ )
26
+
27
+
28
+ def supports_image_training(model: str | ModelInfo | None) -> bool:
29
+ """Return whether a curated model supports image-bearing SFT, GRPO, and OPD."""
30
+ model_id = model.id if isinstance(model, ModelInfo) else model
31
+ return bool(model_id and model_id in _IMAGE_TRAINING_MODELS)
16
32
 
17
33
 
18
34
  def samples_on_policy(algorithm: str) -> bool:
@@ -146,10 +162,10 @@ class ModelInfo:
146
162
 
147
163
  DEFAULT_MODEL = "Qwen/Qwen3.5-4B"
148
164
 
149
- # The pre-quantized FP8 checkpoint each base model's serving engine LOADS (mirrors serving's
150
- # ``src.prequant_config``). Every dense model now serves a Freesolo-OWNED FP8_DYNAMIC checkpoint (no
151
- # community-repo dependence); the 35B VL MoE serves the OFFICIAL Qwen FP8 (it preserves the full
152
- # vision-language model). Informational for the catalog mirror — deploy gating reads only max_lora_rank.
165
+ # the pre-quantized fp8 checkpoint each base model's serving engine loads (mirrors serving's
166
+ # ``src.prequant_config``). every dense model serves a freesolo-owned fp8 checkpoint; the qwen3.6
167
+ # 35b-a3b moe serves the official qwen fp8 variant. informational for the catalog mirror; deploy
168
+ # gating reads only max_lora_rank.
153
169
  SERVING_FP8_MODEL_REPOS: dict[str, str] = {
154
170
  "openbmb/MiniCPM5-1B": "Freesolo-Co/MiniCPM5-1B-FP8",
155
171
  "Qwen/Qwen3.5-0.8B": "Freesolo-Co/Qwen3.5-0.8B-FP8",
@@ -157,6 +173,7 @@ SERVING_FP8_MODEL_REPOS: dict[str, str] = {
157
173
  "Qwen/Qwen3.5-4B": "Freesolo-Co/Qwen3.5-4B-FP8",
158
174
  "Qwen/Qwen3.5-9B": "Freesolo-Co/Qwen3.5-9B-FP8",
159
175
  "Qwen/Qwen3.6-35B-A3B": "Qwen/Qwen3.6-35B-A3B-FP8",
176
+ "Qwen/Qwen3.6-27B": "Freesolo-Co/Qwen3.6-27B-FP8",
160
177
  }
161
178
 
162
179
  MODELS: dict[str, ModelInfo] = {
@@ -182,7 +199,7 @@ MODELS: dict[str, ModelInfo] = {
182
199
  "Qwen/Qwen3.5-0.8B": ModelInfo(
183
200
  id="Qwen/Qwen3.5-0.8B",
184
201
  display_name="Qwen3.5 0.8B",
185
- params="0.9B (text-only fine-tune)",
202
+ params="0.9B",
186
203
  params_b=0.9,
187
204
  vocab_size=248_320,
188
205
  algos=ALGORITHMS,
@@ -201,7 +218,7 @@ MODELS: dict[str, ModelInfo] = {
201
218
  "Qwen/Qwen3.5-2B": ModelInfo(
202
219
  id="Qwen/Qwen3.5-2B",
203
220
  display_name="Qwen3.5 2B",
204
- params="2.3B (text-only fine-tune)",
221
+ params="2.3B",
205
222
  params_b=2.3,
206
223
  vocab_size=248_320,
207
224
  algos=ALGORITHMS,
@@ -219,7 +236,7 @@ MODELS: dict[str, ModelInfo] = {
219
236
  "Qwen/Qwen3.5-4B": ModelInfo(
220
237
  id="Qwen/Qwen3.5-4B",
221
238
  display_name="Qwen3.5 4B",
222
- params="4.7B (text-only fine-tune)",
239
+ params="4.7B",
223
240
  params_b=4.7,
224
241
  vocab_size=248_320,
225
242
  algos=ALGORITHMS,
@@ -241,7 +258,7 @@ MODELS: dict[str, ModelInfo] = {
241
258
  "Qwen/Qwen3.5-9B": ModelInfo(
242
259
  id="Qwen/Qwen3.5-9B",
243
260
  display_name="Qwen3.5 9B",
244
- params="9.7B (text-only fine-tune)",
261
+ params="9.7B",
245
262
  params_b=9.7,
246
263
  vocab_size=248_320,
247
264
  algos=ALGORITHMS,
@@ -264,6 +281,35 @@ MODELS: dict[str, ModelInfo] = {
264
281
  "(two bf16 copies + KV + the 248k-vocab fp32 logits) needs an 80 GB-class card "
265
282
  "(grpo_min_vram_gb floor).",
266
283
  ),
284
+ "Qwen/Qwen3.6-27B": ModelInfo(
285
+ id="Qwen/Qwen3.6-27B",
286
+ display_name="Qwen3.6 27B",
287
+ params="27B dense (multimodal VL, hybrid GDN)",
288
+ params_b=27.0,
289
+ num_layers=64,
290
+ hidden_size=5120,
291
+ vocab_size=248_320,
292
+ algos=("sft", "grpo", "opd"),
293
+ min_vram_gb=80,
294
+ sft_min_vram_gb=80,
295
+ quant="bf16",
296
+ recommended_gpu="A100 PCIe",
297
+ min_disk_gb=160,
298
+ serving=ServingCapacity(
299
+ gpu="H100",
300
+ serve_model_id=SERVING_FP8_MODEL_REPOS["Qwen/Qwen3.6-27B"],
301
+ max_loras=16,
302
+ max_lora_rank=64,
303
+ max_model_len=32768,
304
+ max_num_seqs=8,
305
+ gpu_memory_utilization=0.98,
306
+ ),
307
+ thinking="hybrid",
308
+ notes="Dense 27B multimodal VL checkpoint with image-capable bf16 LoRA training. SFT fits "
309
+ "the 80GB A100 (~54GB weights); colocated GRPO needs the B200 (trainer + vLLM rollout = two "
310
+ "~54GB copies). Serves the owned VL-preserving FP8 on an H100 tier (dense, so no MoE expert "
311
+ "LoRA-buffer multiplier).",
312
+ ),
267
313
  "Qwen/Qwen3.6-35B-A3B": ModelInfo(
268
314
  id="Qwen/Qwen3.6-35B-A3B",
269
315
  display_name="Qwen3.6 35B-A3B (MoE)",
@@ -313,9 +313,8 @@ def cmd_train(args) -> int:
313
313
  runtime_secrets_from_local_env(args.config, keys=spec.environment.secrets) or None
314
314
  )
315
315
  if args.dry_run:
316
- # dry-run is a faithful server-side preview: it sends the same declared secrets and runs the
317
- # same config, warm-start, serving, and cost preflights as a real submit, but allocates no gpu
318
- # and charges nothing. a rejection surfaces as the server's error with exit status 1.
316
+ # dry-run runs submit-time server preflights without importing user code, allocating a gpu,
317
+ # or charging anything. a rejection surfaces as the server's error with exit status 1.
319
318
  try:
320
319
  status = client.create_run(
321
320
  payload,
@@ -330,6 +329,14 @@ def cmd_train(args) -> int:
330
329
  raise ApiError(exc.status, detail) from exc
331
330
  compatibility = status.pop("train_schema_compatibility", None)
332
331
  _print_train_schema_compatibility(compatibility)
332
+ print(
333
+ "dry-run validated: config/schema, model+algorithm compatibility, lora rank, "
334
+ "runtime-secret presence, warm-start source, serving context cap, and cost. it did NOT "
335
+ "import or run your environment.py; dataset loading, start_episode/episode shapes, "
336
+ "reward/scorer, worker imports, model load, and gpu/training are first exercised on the "
337
+ "worker after cold-start.",
338
+ file=sys.stderr,
339
+ )
333
340
  if render.styled():
334
341
  print(
335
342
  render.object_panel(
@@ -700,7 +707,7 @@ def cmd_deployments(args) -> int:
700
707
  return 0
701
708
  print(
702
709
  f"{'RUN ID':<30} {'STEP':<6} {'REVISION':<40} {'STATE':<14} "
703
- f"{'VERIFIED AT':<18} {'OPENAI MODEL':<30} DETAIL"
710
+ f"{'VERIFIED AT':<20} {'OPENAI MODEL':<30} DETAIL"
704
711
  )
705
712
  for row in rows:
706
713
  deployment = row.get("deployment") or {}
@@ -708,14 +715,16 @@ def cmd_deployments(args) -> int:
708
715
  step = deployment.get("checkpoint_step")
709
716
  step_text = "final" if step is None else str(step)
710
717
  verified_at = deployment.get("verified_at")
711
- verified_text = "-" if verified_at is None else str(verified_at)
718
+ verified_text = (
719
+ "-" if verified_at is None else (render._humanize_ts(verified_at) or str(verified_at))
720
+ )
712
721
  revision = str(deployment.get("adapter_revision") or "-")
713
722
  state = str(deployment.get("state") or "-")
714
723
  openai_model = str(deployment.get("openai_model") or run_id)
715
724
  detail = str(deployment.get("error") or deployment.get("detail") or "")[:160]
716
725
  print(
717
726
  f"{run_id:<30} {step_text:<6} {revision:<40} {state:<14} "
718
- f"{verified_text:<18} {openai_model:<30} {detail}"
727
+ f"{verified_text:<20} {openai_model:<30} {detail}"
719
728
  )
720
729
  return 0
721
730
 
@@ -488,7 +488,10 @@ def deployments_table(rows: list[dict]) -> str:
488
488
  ("final" if step is None else str(step), _TEAL),
489
489
  (str(deployment.get("adapter_revision") or "-"), _ACCENT2),
490
490
  (state, color),
491
- ("-" if verified_at is None else str(verified_at), _GRAY),
491
+ (
492
+ "-" if verified_at is None else (_humanize_ts(verified_at) or str(verified_at)),
493
+ _GRAY,
494
+ ),
492
495
  (str(deployment.get("openai_model") or run_id), _GREEN),
493
496
  (detail, _GRAY),
494
497
  ]
@@ -208,9 +208,11 @@ Work in tight, attributable iterations. Each one is a hypothesis:
208
208
  1. Reconstruct state — what's the best run so far, and what have you already tried?
209
209
  2. Form a hypothesis — pick ONE lever and say WHY it will move the metric.
210
210
  3. Change that ONE lever.
211
- 4. Validate `flash train configs/sft.toml --dry-run` (server-side preview: catches config
212
- errors, serving rank/context caps, and warm-start rank mismatches for free — no GPU, no charge;
213
- a paid run on a broken config or an all-zero reward is wasted budget).
211
+ 4. Validate with `flash train configs/sft.toml --dry-run`. This server-side preview checks
212
+ config/schema, model+algorithm compatibility, LoRA rank, runtime-secret presence, warm-start
213
+ source, serving context cap, and cost for free, with no GPU or charge. It does not import or run
214
+ `environment.py`; dataset loading, episode shapes, reward/scorer, worker imports, model load, and
215
+ GPU/training are first exercised on the worker after cold-start.
214
216
  5. Submit — `flash train configs/sft.toml`.
215
217
  6. Judge — read the metric trend AND a sample of real rollouts (see below).
216
218
  7. Keep the best run; revert the change if it didn't beat the noise band. Repeat.
@@ -247,7 +249,7 @@ spending another GPU run:
247
249
 
248
250
  | Issue | Symptom | Mitigation |
249
251
  | --- | --- | --- |
250
- | Environment id is blank or stale | `flash train --dry-run` fails, or the worker uses old reward/data | Run `flash env push --name my-env .` after every environment/data edit and paste the returned id into every config you submit. |
252
+ | Environment id is blank or stale | A blank id fails config validation. A stale published id can pass `flash train --dry-run` because dry-run does not import or run `environment.py`; the worker then uses old reward/data. | Run `flash env push --name my-env .` after every environment/data edit and paste the returned id into every config you submit. |
251
253
  | Local-only env path in config | Config validation says there is no local path mode | Publish first, then use the returned slug in `[environment] id`. `flash train` only runs published env ids, not local paths. |
252
254
  | Config knobs are in the wrong table | Validation rejects `[grpo]`, `[sft]`, or unknown `[train]` keys | Put `epochs`, `group_size`, `max_completion_tokens`, `temperature`, `max_context_tokens`, LoRA, and other training knobs under `[train]`. |
253
255
  | Trying to pin managed infrastructure | `gpu.type`, `train.hf_repo`, or `model_policy` changes do not do what you expected | Treat GPU choice, model policy, and the run artifact repo as managed. Tune the model, algorithm, environment, and `[train]` knobs instead. |
@@ -326,6 +326,7 @@ class _RolloutState:
326
326
  "done",
327
327
  "env_mask",
328
328
  "example",
329
+ "images",
329
330
  "logprobs",
330
331
  "messages",
331
332
  "prompt_ids",
@@ -333,8 +334,9 @@ class _RolloutState:
333
334
  "turns",
334
335
  )
335
336
 
336
- def __init__(self, example, messages, prompt_ids, state, budget):
337
+ def __init__(self, example, messages, prompt_ids, state, budget, images=None):
337
338
  self.example = example
339
+ self.images = images
338
340
  self.messages = messages
339
341
  self.prompt_ids = prompt_ids
340
342
  self.cur_ids = list(prompt_ids) # invariant: cur_ids == prompt_ids + completion_ids so far
@@ -398,23 +400,56 @@ def _advance_after_turn(
398
400
  r.cur_ids.extend(glue)
399
401
 
400
402
 
403
+ def _extract_prompt_images(messages: list[dict]) -> list[object]:
404
+ """Extract decoded PIL images from TRL-injected prompt content blocks."""
405
+ from PIL import Image
406
+
407
+ images = []
408
+ for message in messages:
409
+ content = message.get("content")
410
+ if not isinstance(content, list):
411
+ continue
412
+ for part in content:
413
+ if not isinstance(part, dict) or part.get("type") not in {
414
+ "image",
415
+ "image_url",
416
+ "input_image",
417
+ }:
418
+ continue
419
+ image = part.get("image")
420
+ if not isinstance(image, Image.Image):
421
+ raise ValueError(
422
+ "multimodal rollout image blocks must carry a PIL image at part['image']"
423
+ )
424
+ images.append(image)
425
+ return images
426
+
427
+
401
428
  def _build_rollout_states(
402
429
  examples: list[dict],
403
430
  active_env,
404
431
  render: Callable[[list, bool], list[int]],
405
432
  engine_max_len: int | None,
433
+ *,
434
+ extract_images: bool = False,
435
+ rollout_prompts: list[list[dict]] | None = None,
406
436
  ) -> list[_RolloutState]:
407
437
  """Initialise one :class:`_RolloutState` per example for :func:`rollout_async`."""
438
+ if rollout_prompts is not None and len(rollout_prompts) != len(examples):
439
+ raise ValueError("rollout prompts must align one-to-one with examples")
408
440
  rollouts: list[_RolloutState] = []
409
- for example in examples:
441
+ for index, example in enumerate(examples):
410
442
  state = active_env.new_rollout_state(example)
411
443
  initial_messages = state.get("prompt") or state.get("messages")
444
+ if rollout_prompts is not None:
445
+ initial_messages = rollout_prompts[index]
412
446
  if not isinstance(initial_messages, list):
413
447
  raise KeyError("multi-turn rollout state must include prompt or messages")
414
448
  messages = [dict(m) for m in initial_messages]
449
+ images = _extract_prompt_images(messages) if extract_images else None
415
450
  prompt_ids = render(messages, True)
416
451
  budget = (engine_max_len - len(prompt_ids) - 8) if engine_max_len else None
417
- rollouts.append(_RolloutState(example, messages, prompt_ids, state, budget))
452
+ rollouts.append(_RolloutState(example, messages, prompt_ids, state, budget, images))
418
453
  return rollouts
419
454
 
420
455
 
@@ -481,6 +516,7 @@ class _LogicalRequest:
481
516
  prefix_ids: tuple[int, ...]
482
517
  max_tokens: int
483
518
  initial: bool
519
+ images: list[object] | None
484
520
  attempts: int = 0
485
521
  started_at: float = 0.0
486
522
 
@@ -490,7 +526,7 @@ def rollout_async(
490
526
  examples: list[dict],
491
527
  active_env,
492
528
  render: Callable[[list, bool], list[int]],
493
- submit: Callable[[str, list[int], int, bool], None],
529
+ submit: Callable[[str, list[int], int, bool, list[object] | None], None],
494
530
  poll: Callable[[], list[RolloutCompletion]],
495
531
  busy: Callable[[], bool],
496
532
  abort: Callable[[list[str]], None],
@@ -498,6 +534,8 @@ def rollout_async(
498
534
  max_turns: int,
499
535
  per_turn_max_tokens: int,
500
536
  engine_max_len: int | None = None,
537
+ extract_images: bool = False,
538
+ rollout_prompts: list[list[dict]] | None = None,
501
539
  request_timeout_seconds: float | None = None,
502
540
  request_max_attempts: int = 2,
503
541
  monotonic: Callable[[], float] = time.monotonic,
@@ -516,7 +554,14 @@ def rollout_async(
516
554
  if request_timeout_seconds is not None and request_timeout_seconds <= 0:
517
555
  raise ValueError("request_timeout_seconds must be positive when set")
518
556
 
519
- rollouts = _build_rollout_states(examples, active_env, render, engine_max_len)
557
+ rollouts = _build_rollout_states(
558
+ examples,
559
+ active_env,
560
+ render,
561
+ engine_max_len,
562
+ extract_images=extract_images,
563
+ rollout_prompts=rollout_prompts,
564
+ )
520
565
  by_id: dict[str, _LogicalRequest] = {}
521
566
  to_env: queue.Queue = queue.Queue()
522
567
  to_submit: queue.Queue = queue.Queue()
@@ -527,7 +572,13 @@ def rollout_async(
527
572
  logical.started_at = monotonic()
528
573
  by_id[req_id] = logical
529
574
  try:
530
- submit(req_id, list(logical.prefix_ids), logical.max_tokens, logical.initial)
575
+ submit(
576
+ req_id,
577
+ list(logical.prefix_ids),
578
+ logical.max_tokens,
579
+ logical.initial,
580
+ logical.images,
581
+ )
531
582
  except Exception:
532
583
  by_id.pop(req_id, None)
533
584
  with contextlib.suppress(Exception):
@@ -541,6 +592,7 @@ def rollout_async(
541
592
  prefix_ids=tuple(prefix),
542
593
  max_tokens=int(max_new),
543
594
  initial=bool(initial),
595
+ images=r.images,
544
596
  )
545
597
  )
546
598
 
@@ -718,6 +770,8 @@ def build_rollout_func(
718
770
  active_env,
719
771
  tok,
720
772
  examples_by_key: dict,
773
+ processor=None,
774
+ multimodal: bool = False,
721
775
  max_completion: int,
722
776
  max_turns: int,
723
777
  temperature: float,
@@ -751,18 +805,47 @@ def build_rollout_func(
751
805
 
752
806
  _SOParams = StructuredOutputsParams
753
807
 
754
- _render_cache = _LRUCache(8192)
755
-
756
- def render(messages: list, add_generation_prompt: bool) -> list[int]:
757
- cache_key = (
758
- f"{add_generation_prompt}\x00{json.dumps(messages, sort_keys=True, default=str)}"
808
+ image_pad_id = None
809
+ if multimodal:
810
+ if processor is None:
811
+ raise ValueError("multimodal rollout requires a processor")
812
+ from flash.multimodal import (
813
+ collapse_image_pad_runs,
814
+ multimodal_prompt_key,
815
+ resolve_image_pad_token_id,
759
816
  )
760
- cached = _render_cache.get(cache_key)
761
- if cached is not None:
762
- return cached
763
- ids = render_message_ids(tok, messages, add_generation_prompt, thinking=thinking)
764
- _render_cache.put(cache_key, ids)
765
- return ids
817
+
818
+ image_pad_id = resolve_image_pad_token_id(processor, tok)
819
+
820
+ def render(messages: list, add_generation_prompt: bool) -> list[int]:
821
+ rendered = processor.apply_chat_template(
822
+ messages,
823
+ add_generation_prompt=add_generation_prompt,
824
+ tokenize=True,
825
+ return_dict=True,
826
+ enable_thinking=thinking,
827
+ )
828
+ input_ids = rendered["input_ids"]
829
+ if hasattr(input_ids, "tolist"):
830
+ input_ids = input_ids.tolist()
831
+ ids = list(input_ids)
832
+ if ids and isinstance(ids[0], (list, tuple)):
833
+ ids = list(ids[0])
834
+ return [int(token_id) for token_id in ids]
835
+
836
+ else:
837
+ _render_cache = _LRUCache(8192)
838
+
839
+ def render(messages: list, add_generation_prompt: bool) -> list[int]:
840
+ cache_key = (
841
+ f"{add_generation_prompt}\x00{json.dumps(messages, sort_keys=True, default=str)}"
842
+ )
843
+ cached = _render_cache.get(cache_key)
844
+ if cached is not None:
845
+ return cached
846
+ ids = render_message_ids(tok, messages, add_generation_prompt, thinking=thinking)
847
+ _render_cache.put(cache_key, ids)
848
+ return ids
766
849
 
767
850
  env_glue = make_env_glue(tok, thinking=thinking)
768
851
 
@@ -774,7 +857,13 @@ def build_rollout_func(
774
857
  vocab_size = _engine_vocab_size(engine)
775
858
  active_ids: set[str] = set()
776
859
 
777
- def submit(req_id: str, prefix_ids: list[int], max_tokens: int, initial: bool) -> None:
860
+ def submit(
861
+ req_id: str,
862
+ prefix_ids: list[int],
863
+ max_tokens: int,
864
+ initial: bool,
865
+ images: list[object] | None,
866
+ ) -> None:
778
867
  """Enqueue one assistant-turn request."""
779
868
  if not prefix_ids:
780
869
  raise ValueError(
@@ -801,9 +890,21 @@ def build_rollout_func(
801
890
  # env contract has no per-turn schema channel; unconstrained env/tool turns are
802
891
  # not generated here.
803
892
  sp_kwargs["structured_outputs"] = _SOParams(**copy.deepcopy(structured_outputs))
804
- llm_engine.add_request(
805
- req_id, {"prompt_token_ids": list(prefix_ids)}, SamplingParams(**sp_kwargs)
806
- )
893
+ if images:
894
+ if image_pad_id is None:
895
+ raise ValueError("multimodal rollout is missing the image-pad token id")
896
+ collapsed = collapse_image_pad_runs(list(prefix_ids), image_pad_id, len(images))
897
+ prompt = {
898
+ "prompt_token_ids": collapsed,
899
+ "multi_modal_data": {"image": images if len(images) > 1 else images[0]},
900
+ }
901
+ # suppress the image-pad token during generation: a stray pad emitted into the
902
+ # assistant turn would corrupt the next turn's collapse_image_pad_runs count and
903
+ # misalign vision data, aborting the rollout. mirrors the single-turn opd guard.
904
+ sp_kwargs["logit_bias"] = {int(image_pad_id): -100.0}
905
+ else:
906
+ prompt = {"prompt_token_ids": list(prefix_ids)}
907
+ llm_engine.add_request(req_id, prompt, SamplingParams(**sp_kwargs))
807
908
  active_ids.add(req_id)
808
909
 
809
910
  def poll() -> list[RolloutCompletion]:
@@ -838,7 +939,12 @@ def build_rollout_func(
838
939
  if sleep_mode:
839
940
  engine.wake_up(tags=["kv_cache"])
840
941
  woke = True
841
- examples = [examples_by_key.get(_prompt_key(p), {"prompt": p}) for p in prompts]
942
+ if multimodal:
943
+ examples = [
944
+ examples_by_key.get(multimodal_prompt_key(p), {"prompt": p}) for p in prompts
945
+ ]
946
+ else:
947
+ examples = [examples_by_key.get(_prompt_key(p), {"prompt": p}) for p in prompts]
842
948
  rollouts = rollout_async(
843
949
  examples=examples,
844
950
  active_env=active_env,
@@ -850,6 +956,8 @@ def build_rollout_func(
850
956
  max_turns=max_turns,
851
957
  per_turn_max_tokens=max_completion,
852
958
  engine_max_len=engine_max_len,
959
+ extract_images=multimodal,
960
+ rollout_prompts=prompts if multimodal else None,
853
961
  abort=abort,
854
962
  request_timeout_seconds=request_timeout_seconds,
855
963
  request_max_attempts=request_max_attempts,
@@ -35,6 +35,7 @@ class TeacherModel:
35
35
  # serverless models — confirm against the live fireworks.ai pricing before relying on the
36
36
  # `flash train` cost quote.
37
37
  usd_per_1m: tuple[float, float]
38
+ supports_images: bool = False
38
39
 
39
40
 
40
41
  # The alias used when [train] omits teacher_model — the historical fixed teacher, unchanged.
@@ -64,6 +65,7 @@ TEACHER_MODELS: dict[str, TeacherModel] = {
64
65
  model_id="accounts/fireworks/models/kimi-k2p6",
65
66
  display_name="Kimi K2.6",
66
67
  usd_per_1m=(0.95, 4.00),
68
+ supports_images=True,
67
69
  ),
68
70
  }
69
71
 
@@ -75,6 +77,11 @@ def normalize_teacher_alias(value: str) -> str:
75
77
  return "-".join(str(value).strip().lower().replace("_", " ").replace("-", " ").split())
76
78
 
77
79
 
80
+ def teacher_supports_images(value: str) -> bool:
81
+ """Return whether the resolved managed teacher accepts image-conditioned echo scoring."""
82
+ return resolve_teacher(value).supports_images
83
+
84
+
78
85
  def resolve_teacher(value: str) -> TeacherModel:
79
86
  """Resolve a ``[train] teacher_model`` value to its catalog entry.
80
87
 
@@ -45,10 +45,16 @@ def build_grpo_prompt_dataset(prompts: list[dict]) -> tuple[list[dict], list]:
45
45
 
46
46
  Dataset.from_list infers one type per field across ALL rows; mixed-type metadata
47
47
  (e.g. int vs str in the same field) causes ArrowInvalid at RL startup. Fix: keep only
48
- trivially-typed columns (prompt + integer example_idx); reward_fn maps the index back.
48
+ trivially typed columns (prompt, integer example_idx, and optional image descriptor strings);
49
+ reward_fn maps the index back.
49
50
  """
50
51
  examples = [p["example"] for p in prompts]
51
- rows = [{"prompt": p["prompt"], "example_idx": i} for i, p in enumerate(prompts)]
52
+ rows = []
53
+ for i, prompt in enumerate(prompts):
54
+ row = {"prompt": prompt["prompt"], "example_idx": i}
55
+ if "images" in prompt:
56
+ row["images"] = list(prompt["images"])
57
+ rows.append(row)
52
58
  return rows, examples
53
59
 
54
60