freesolo-flash-dev 1.0.6__tar.gz → 1.0.7__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 (359) hide show
  1. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/PKG-INFO +1 -1
  2. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/cost/analytical.py +3 -3
  3. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/cost/facts.py +25 -0
  4. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/engine/vram.py +11 -27
  5. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/engine/worker/opd.py +118 -11
  6. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/pyproject.toml +2 -2
  7. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_allocator.py +38 -7
  8. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_cost_estimate.py +30 -0
  9. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_cost_hardware.py +19 -0
  10. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_opd.py +252 -60
  11. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/uv.lock +1 -1
  12. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/.claude/skills/verify/SKILL.md +0 -0
  13. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/.dockerignore +0 -0
  14. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/.env.example +0 -0
  15. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/.github/workflows/auto-rebake.yml +0 -0
  16. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/.github/workflows/bake-kernel-cache.yml +0 -0
  17. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/.github/workflows/ci.yml +0 -0
  18. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/.github/workflows/main-source-guard.yml +0 -0
  19. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/.github/workflows/notify-tests-repo.yml +0 -0
  20. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/.github/workflows/publish-dev.yml +0 -0
  21. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/.github/workflows/publish-image.yml +0 -0
  22. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/.github/workflows/publish.yml +0 -0
  23. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/.github/workflows/version-parity.yml +0 -0
  24. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/.github/workflows/worker-image.yml +0 -0
  25. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/.gitignore +0 -0
  26. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/Dockerfile +0 -0
  27. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/Dockerfile.worker +0 -0
  28. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/LICENSE +0 -0
  29. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/README.md +0 -0
  30. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/build/kernel_cache/.gitignore +0 -0
  31. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/build/kernel_cache/.keep +0 -0
  32. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/docker/Dockerfile.kernelcache +0 -0
  33. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/docker/Dockerfile.kernelcache.relayer +0 -0
  34. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/docker/bake_kernel_cache.py +0 -0
  35. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/docker/bake_pod_entry.py +0 -0
  36. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/docker/kernel_fingerprint.py +0 -0
  37. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/docker/make_rp_handler.py +0 -0
  38. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/__init__.py +0 -0
  39. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/_channel.py +0 -0
  40. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/_fileio.py +0 -0
  41. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/_logging.py +0 -0
  42. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/_update_check.py +0 -0
  43. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/adapter_artifacts.py +0 -0
  44. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/catalog.py +0 -0
  45. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/cli/__init__.py +0 -0
  46. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/cli/__main__.py +0 -0
  47. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/cli/_tty.py +0 -0
  48. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/cli/commands.py +0 -0
  49. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/cli/env_setup.py +0 -0
  50. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/cli/envpush.py +0 -0
  51. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/cli/render.py +0 -0
  52. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/cli/training_doc.py +0 -0
  53. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/client/__init__.py +0 -0
  54. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/client/config.py +0 -0
  55. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/client/http.py +0 -0
  56. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/client/runtime_secrets.py +0 -0
  57. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/client/specs.py +0 -0
  58. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/cost/__init__.py +0 -0
  59. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/cost/spec.py +0 -0
  60. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/cost/types.py +0 -0
  61. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/diagnostics.py +0 -0
  62. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/engine/__init__.py +0 -0
  63. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/engine/accounting.py +0 -0
  64. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/engine/chalk_kernels.py +0 -0
  65. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/engine/multiturn_reward_scoring.py +0 -0
  66. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/engine/multiturn_rollout.py +0 -0
  67. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/engine/recipe.py +0 -0
  68. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/engine/steps.py +0 -0
  69. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/engine/structured_outputs.py +0 -0
  70. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/engine/worker/__init__.py +0 -0
  71. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/engine/worker/__main__.py +0 -0
  72. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/engine/worker/_pkg.py +0 -0
  73. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/engine/worker/adapter.py +0 -0
  74. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/engine/worker/decoding.py +0 -0
  75. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/engine/worker/finalize.py +0 -0
  76. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/engine/worker/gpu_setup.py +0 -0
  77. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/engine/worker/grpo.py +0 -0
  78. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/engine/worker/grpo_multimodal.py +0 -0
  79. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/engine/worker/grpo_perturn_trainer.py +0 -0
  80. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/engine/worker/heartbeat.py +0 -0
  81. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/engine/worker/hf.py +0 -0
  82. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/engine/worker/kernel_warmup.py +0 -0
  83. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/engine/worker/lora.py +0 -0
  84. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/engine/worker/opd_gkd.py +0 -0
  85. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/engine/worker/opd_vllm.py +0 -0
  86. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/engine/worker/packing.py +0 -0
  87. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/engine/worker/perf/__init__.py +0 -0
  88. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/engine/worker/perf/attn.py +0 -0
  89. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/engine/worker/perf/diagnostics.py +0 -0
  90. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/engine/worker/perf/lifecycle.py +0 -0
  91. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/engine/worker/perf/liger.py +0 -0
  92. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/engine/worker/perf/loraplus.py +0 -0
  93. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/engine/worker/perf/memory.py +0 -0
  94. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/engine/worker/rl.py +0 -0
  95. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/engine/worker/rng.py +0 -0
  96. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/engine/worker/sft.py +0 -0
  97. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/engine/worker/teacher.py +0 -0
  98. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/engine/worker/tokenizer_align.py +0 -0
  99. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/engine/worker/wandb_log.py +0 -0
  100. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/engine/worker_entrypoint.py +0 -0
  101. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/envs/__init__.py +0 -0
  102. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/envs/adapter.py +0 -0
  103. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/envs/archive.py +0 -0
  104. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/envs/archive_policy.py +0 -0
  105. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/envs/base.py +0 -0
  106. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/envs/loader.py +0 -0
  107. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/envs/pull.py +0 -0
  108. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/envs/registry.py +0 -0
  109. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/lora_rank.py +0 -0
  110. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/multimodal.py +0 -0
  111. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/opd_retry_contract.py +0 -0
  112. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/providers/__init__.py +0 -0
  113. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/providers/_auth.py +0 -0
  114. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/providers/_deadline.py +0 -0
  115. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/providers/_hf_artifacts.py +0 -0
  116. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/providers/_hf_retry.py +0 -0
  117. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/providers/_http.py +0 -0
  118. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/providers/_instance.py +0 -0
  119. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/providers/_instance_bootstrap.py +0 -0
  120. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/providers/_instance_poll.py +0 -0
  121. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/providers/_instance_provider.py +0 -0
  122. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/providers/_poll.py +0 -0
  123. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/providers/_worker.py +0 -0
  124. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/providers/allocator.py +0 -0
  125. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/providers/base.py +0 -0
  126. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/providers/lambdalabs/__init__.py +0 -0
  127. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/providers/lambdalabs/api.py +0 -0
  128. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/providers/lambdalabs/auth.py +0 -0
  129. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/providers/lambdalabs/gpus.py +0 -0
  130. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/providers/lambdalabs/jobs/__init__.py +0 -0
  131. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/providers/lambdalabs/jobs/builders.py +0 -0
  132. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/providers/lambdalabs/preflight.py +0 -0
  133. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/providers/lambdalabs/pricing.py +0 -0
  134. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/providers/preflight.py +0 -0
  135. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/providers/realized.py +0 -0
  136. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/providers/runpod/__init__.py +0 -0
  137. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/providers/runpod/api.py +0 -0
  138. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/providers/runpod/auth.py +0 -0
  139. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/providers/runpod/cost.py +0 -0
  140. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/providers/runpod/gpus.py +0 -0
  141. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/providers/runpod/jobs.py +0 -0
  142. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/providers/runpod/keys.py +0 -0
  143. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/providers/runpod/preflight.py +0 -0
  144. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/providers/runpod/preload.py +0 -0
  145. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/providers/runpod/pricing.py +0 -0
  146. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/providers/runpod/slots.py +0 -0
  147. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/providers/runpod/train/__init__.py +0 -0
  148. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/providers/runpod/train/endpoints.py +0 -0
  149. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/providers/vast/__init__.py +0 -0
  150. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/providers/vast/api.py +0 -0
  151. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/providers/vast/auth.py +0 -0
  152. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/providers/vast/jobs/__init__.py +0 -0
  153. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/providers/vast/jobs/builders.py +0 -0
  154. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/providers/vast/preflight.py +0 -0
  155. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/providers/vast/pricing.py +0 -0
  156. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/py.typed +0 -0
  157. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/runner/__init__.py +0 -0
  158. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/runner/checkpoints.py +0 -0
  159. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/runner/deploy.py +0 -0
  160. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/runner/lifecycle.py +0 -0
  161. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/runner/verified_revisions.py +0 -0
  162. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/schema/__init__.py +0 -0
  163. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/schema/fields.py +0 -0
  164. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/serve/__init__.py +0 -0
  165. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/serve/deploy.py +0 -0
  166. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/serve/export.py +0 -0
  167. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/serve/pricing.py +0 -0
  168. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/serve/urls.py +0 -0
  169. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/server/__init__.py +0 -0
  170. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/server/__main__.py +0 -0
  171. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/server/_deps.py +0 -0
  172. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/server/_internal_client.py +0 -0
  173. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/server/_locks.py +0 -0
  174. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/server/_runtime.py +0 -0
  175. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/server/app.py +0 -0
  176. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/server/auth.py +0 -0
  177. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/server/billing.py +0 -0
  178. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/server/billing_retry.py +0 -0
  179. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/server/checkpoints.py +0 -0
  180. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/server/db.py +0 -0
  181. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/server/environment_registry.py +0 -0
  182. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/server/envs.py +0 -0
  183. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/server/reconcile.py +0 -0
  184. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/server/repo_cleanup.py +0 -0
  185. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/server/routes/__init__.py +0 -0
  186. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/server/routes/envs.py +0 -0
  187. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/server/routes/meta.py +0 -0
  188. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/server/routes/runs.py +0 -0
  189. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/server/routes/serving.py +0 -0
  190. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/server/run_registry.py +0 -0
  191. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/flash/spec.py +0 -0
  192. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/infisical-entrypoint.sh +0 -0
  193. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/scripts/build_dev_dist.py +0 -0
  194. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/__init__.py +0 -0
  195. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/_helpers/__init__.py +0 -0
  196. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/_helpers/runner.py +0 -0
  197. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/_helpers/specs.py +0 -0
  198. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/_helpers/vast.py +0 -0
  199. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/conftest.py +0 -0
  200. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/fixtures/math_eval.jsonl +0 -0
  201. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/fixtures/math_train.jsonl +0 -0
  202. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/live/__init__.py +0 -0
  203. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/live/conftest.py +0 -0
  204. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/live/test_lambda_live.py +0 -0
  205. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/live/test_runpod_live.py +0 -0
  206. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/live/test_vast_live.py +0 -0
  207. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_accounting_coverage.py +0 -0
  208. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_agent_flash_cli_contract.py +0 -0
  209. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_algorithms.py +0 -0
  210. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_async_uploads.py +0 -0
  211. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_b200_rollout_opt.py +0 -0
  212. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_backend_jobspec_contract.py +0 -0
  213. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_base_model_provenance.py +0 -0
  214. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_billing_retry.py +0 -0
  215. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_cancel_remote.py +0 -0
  216. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_catalog_consistency.py +0 -0
  217. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_chalk_kernels.py +0 -0
  218. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_charge_pricing.py +0 -0
  219. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_checkpoints.py +0 -0
  220. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_cli_commands.py +0 -0
  221. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_cli_commands_coverage.py +0 -0
  222. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_cli_errors.py +0 -0
  223. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_cli_estimate.py +0 -0
  224. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_cli_help.py +0 -0
  225. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_cli_main_coverage.py +0 -0
  226. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_cli_managed.py +0 -0
  227. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_cli_render_coverage.py +0 -0
  228. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_cli_render_theme.py +0 -0
  229. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_client.py +0 -0
  230. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_client_server_integration.py +0 -0
  231. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_client_stream_reads.py +0 -0
  232. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_config_overrides.py +0 -0
  233. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_controlled_experiment_repairs.py +0 -0
  234. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_cost_analytical.py +0 -0
  235. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_cost_equation.py +0 -0
  236. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_cost_models.py +0 -0
  237. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_cost_rewards.py +0 -0
  238. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_deploy_latency.py +0 -0
  239. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_dev_channel.py +0 -0
  240. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_disk_gb.py +0 -0
  241. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_endpoint_name.py +0 -0
  242. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_env_cache_evict.py +0 -0
  243. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_env_delete.py +0 -0
  244. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_env_download.py +0 -0
  245. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_env_download_api.py +0 -0
  246. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_env_no_install.py +0 -0
  247. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_env_publish.py +0 -0
  248. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_env_pull.py +0 -0
  249. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_env_pull_coverage.py +0 -0
  250. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_env_pull_loader_coverage.py +0 -0
  251. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_env_pull_managed_control_plane.py +0 -0
  252. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_env_push.py +0 -0
  253. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_env_rate_limit_resolve.py +0 -0
  254. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_envs_coverage.py +0 -0
  255. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_export.py +0 -0
  256. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_flash_mvp.py +0 -0
  257. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_flash_worker.py +0 -0
  258. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_flashinfer_cache_dirs.py +0 -0
  259. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_github_urlopen_retry.py +0 -0
  260. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_gpus.py +0 -0
  261. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_grpo_mask_aware.py +0 -0
  262. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_grpo_params.py +0 -0
  263. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_grpo_perturn.py +0 -0
  264. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_grpo_sleep_gate.py +0 -0
  265. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_health_liveness.py +0 -0
  266. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_hf_retry.py +0 -0
  267. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_idle_endpoint_reaper.py +0 -0
  268. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_instance_bootstrap_coverage.py +0 -0
  269. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_internal_client.py +0 -0
  270. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_jobs.py +0 -0
  271. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_kernel_cache.py +0 -0
  272. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_kernel_fingerprint.py +0 -0
  273. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_kv_util.py +0 -0
  274. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_lambda_api_coverage.py +0 -0
  275. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_lambda_runner.py +0 -0
  276. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_logging.py +0 -0
  277. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_login_perms.py +0 -0
  278. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_lora_rank_coverage.py +0 -0
  279. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_lora_rank_preflight.py +0 -0
  280. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_managed_hf_repo.py +0 -0
  281. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_metrics_schema_agent_contract.py +0 -0
  282. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_mig_guard.py +0 -0
  283. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_multimodal_input_grads.py +0 -0
  284. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_multimodal_training.py +0 -0
  285. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_multiturn_image_rollout.py +0 -0
  286. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_multiturn_per_turn_reward.py +0 -0
  287. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_multiturn_rollout.py +0 -0
  288. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_multiturn_rollout_coverage.py +0 -0
  289. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_multiturn_rollout_records.py +0 -0
  290. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_multiturn_rollout_request_policy.py +0 -0
  291. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_named_reward_metrics.py +0 -0
  292. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_oom_escalate_gpu.py +0 -0
  293. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_opd_full_state_resume.py +0 -0
  294. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_opd_resume_safety.py +0 -0
  295. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_opd_vllm.py +0 -0
  296. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_open_model_policy.py +0 -0
  297. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_orchestrator_flash.py +0 -0
  298. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_packing.py +0 -0
  299. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_packing_coverage.py +0 -0
  300. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_poll_helpers.py +0 -0
  301. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_preflight.py +0 -0
  302. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_provider_preflight_coverage.py +0 -0
  303. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_provider_routing.py +0 -0
  304. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_provider_teardown_robustness.py +0 -0
  305. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_providers_symmetry.py +0 -0
  306. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_realized_cost.py +0 -0
  307. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_repo_cleanup.py +0 -0
  308. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_resolve_params_b.py +0 -0
  309. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_resume_on_retry.py +0 -0
  310. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_runmgmt.py +0 -0
  311. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_runpod_api_delete.py +0 -0
  312. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_runpod_key_fingerprint.py +0 -0
  313. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_runpod_key_waterfall.py +0 -0
  314. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_runpod_slots.py +0 -0
  315. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_serve.py +0 -0
  316. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_server_api.py +0 -0
  317. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_server_app_coverage.py +0 -0
  318. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_server_auth_singleflight.py +0 -0
  319. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_server_billing.py +0 -0
  320. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_server_checkpoints_coverage.py +0 -0
  321. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_server_db.py +0 -0
  322. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_server_envs_coverage.py +0 -0
  323. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_server_locks_coverage.py +0 -0
  324. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_serving_context_preflight.py +0 -0
  325. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_serving_contract.py +0 -0
  326. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_serving_pricing_coverage.py +0 -0
  327. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_serving_schema_coverage.py +0 -0
  328. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_sft_activation_shaping.py +0 -0
  329. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_sft_example_selection.py +0 -0
  330. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_sft_gc_off.py +0 -0
  331. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_sft_max_context.py +0 -0
  332. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_sft_tokenize_cache.py +0 -0
  333. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_single_turn_multimodal_grpo.py +0 -0
  334. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_spec_and_validation.py +0 -0
  335. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_structured_outputs.py +0 -0
  336. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_thinking_config.py +0 -0
  337. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_train_context_preflight.py +0 -0
  338. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_training_controls.py +0 -0
  339. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_update_check.py +0 -0
  340. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_vast_api.py +0 -0
  341. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_vast_api_coverage.py +0 -0
  342. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_vast_offers.py +0 -0
  343. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_vast_provider_coverage.py +0 -0
  344. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_vast_runner.py +0 -0
  345. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_verified_revisions.py +0 -0
  346. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_verifiers.py +0 -0
  347. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_version.py +0 -0
  348. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_vl_warmstart_adapter_keys.py +0 -0
  349. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_wandb_log_coverage.py +0 -0
  350. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_wandb_naming.py +0 -0
  351. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_warmstart_adapter_download.py +0 -0
  352. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_warmstart_cross_repo.py +0 -0
  353. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_weight_cache.py +0 -0
  354. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_worker_dryrun.py +0 -0
  355. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_worker_hardexit.py +0 -0
  356. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_worker_image.py +0 -0
  357. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_worker_init_heartbeat.py +0 -0
  358. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_worker_stack.py +0 -0
  359. {freesolo_flash_dev-1.0.6 → freesolo_flash_dev-1.0.7}/tests/test_worker_thinking.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: freesolo-flash-dev
3
- Version: 1.0.6
3
+ Version: 1.0.7
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
@@ -14,8 +14,8 @@ from flash.providers.allocator import required_vram_gb, vram_headroom
14
14
  from .facts import (
15
15
  active_params_b,
16
16
  download_weight_gb,
17
+ effective_train_tflops,
17
18
  gpu_hourly_usd,
18
- gpu_tflops,
19
19
  gpu_vram_gb,
20
20
  model_quant,
21
21
  pick_gpu,
@@ -150,7 +150,7 @@ def compile_seconds(config: RunConfig, gpu: str) -> float:
150
150
  def seconds_per_step(config: RunConfig, gpu: str) -> float:
151
151
  """Steady-state wall time for one optimizer step on ``gpu``."""
152
152
  n = config.normalized()
153
- peak = gpu_tflops(gpu) * 1e12 # FLOP/s
153
+ peak = effective_train_tflops(gpu) * 1e12 # FLOP/s (realized training throughput; see facts)
154
154
  # An MoE's per-step wall scales with TOTAL params (routing + all-expert coordination + grouped
155
155
  # GEMM under-utilization), not the tiny active-param FLOPs; dense models keep active (== total).
156
156
  moe = _is_moe(n.model_id)
@@ -198,7 +198,7 @@ def sft_seconds_for_tokens(config: RunConfig, gpu: str, train_tokens: float) ->
198
198
  moe = _is_moe(n.model_id)
199
199
  params = (total_params_b(n.model_id) if moe else active_params_b(n.model_id)) * 1e9
200
200
  mfu = MFU_SFT_TRAIN_MOE if moe else MFU_SFT_TRAIN
201
- peak = gpu_tflops(gpu) * 1e12
201
+ peak = effective_train_tflops(gpu) * 1e12
202
202
  flops = SFT_FLOPS_PER_TOKEN_PER_PARAM * params * train_tokens
203
203
  return flops / (peak * mfu)
204
204
 
@@ -30,6 +30,31 @@ def gpu_tflops(name: str) -> float:
30
30
  return GPU_COMPUTE_TFLOPS.get(name, _DEFAULT_TFLOPS)
31
31
 
32
32
 
33
+ # realized TRAINING throughput sits well below peak when a class's training kernels don't reach it.
34
+ # b200 (sm100) has no arch-tuned training kernels yet and falls back to the same portable paths as
35
+ # h200 (sm90), so its 2.25 pflops dense-bf16 peak does not materialize for training -- realized
36
+ # throughput is h200-class (and frequently lower for rl/grpo). cap b200 at h200 so the analytical
37
+ # cost model does not rank it as faster/cheaper than h200 on peak flops alone (it is not, and is
38
+ # often slower). this is a conservative floor: it never prices b200 above h200-equivalent training
39
+ # time, so it cannot over-charge, and it removes the "b200 looks cheapest" inversion at the source.
40
+ # refine per-workload once real b200 training samples exist. the vram/serving paths keep the true
41
+ # peak via gpu_tflops; only the training-time model uses this.
42
+ _TRAIN_TFLOPS_CAP: dict[str, float] = {
43
+ "B200": 990.0, # h200-class realized training throughput, not the 2250 dense-bf16 peak
44
+ }
45
+
46
+
47
+ def effective_train_tflops(name: str) -> float:
48
+ """Realized bf16 TRAINING throughput for the analytical cost model.
49
+
50
+ Equal to ``gpu_tflops`` (peak) except where a class's training kernels fall short of peak: b200
51
+ training falls back to portable kernels, so it is capped at h200-class throughput rather than
52
+ its raw 2.25 pflops peak."""
53
+ peak = gpu_tflops(name)
54
+ cap = _TRAIN_TFLOPS_CAP.get(name)
55
+ return min(peak, cap) if cap is not None else peak
56
+
57
+
33
58
  def gpu_hourly_usd(
34
59
  name: str,
35
60
  provider: str | None = None,
@@ -27,6 +27,8 @@ _BYTES_PER_PARAM = {
27
27
  _BASE_OVERHEAD_GB = 4.0
28
28
  _ACT_COEF = 0.12
29
29
  _SFT_PER_DEVICE_BS_DEFAULT = 4
30
+ OPD_CE_CHUNK_SIZE = 64
31
+ _OPD_CE_PEAK_BYTES_PER_LOGIT = 16
30
32
 
31
33
 
32
34
  def _sft_per_device_bs() -> int:
@@ -112,7 +114,7 @@ def opd_rollout_concurrency(prompts_per_step: int = 1, group_size: int = 1) -> i
112
114
 
113
115
 
114
116
  def opd_loss_microbatch(params_b: float, prompts_per_step: int = 1, group_size: int = 1) -> int:
115
- """Loss microbatch size used by OPD's dense-logit GKD backward.
117
+ """Loss microbatch size used by OPD's GKD backward.
116
118
 
117
119
  Keep this in lockstep with worker.opd._opd_loss_microbatch_size without importing the worker from
118
120
  the sizing path. Small/medium catalog models run up to four loss samples per forward; 35B-class
@@ -370,35 +372,17 @@ def estimate_vram_gb(
370
372
  rollout = weights + max(
371
373
  _KV_CAP, _resident_kv_gb(eff_b, seq_len, rollout_concurrency, fp8_kv=fp8_kv)
372
374
  )
373
- # opd's gkd loss forward materializes DENSE logits there is NO fused cross-entropy: the
374
- # student forward yields full-sequence bf16 logits [seq, vocab], then the completion rows are
375
- # gathered in fp32 [completion, vocab] for the logsumexp. The SFT fused path budgets ZERO
376
- # vocab logits for >=3B models, so a long-completion opd job (e.g. max_tokens=8192) would be
377
- # sized for an under-capacity card and OOM.
378
- #
379
- # Mirror opd_rollout_seq_len / run_opd's completion resolution: explicit max_tokens, else the
380
- # OPD recipe default (thinking uses the longer max_completion_len_thinking). A GRPO-style
381
- # min(seq_len, 1024) fallback would UNDER-budget a thinking opd job (1536-token completions).
375
+ # text opd projects only completion-prediction hidden states and computes exact full-vocabulary
376
+ # ce in checkpointed chunks. image samples still use the dense full-sequence logits path and are
377
+ # processed one at a time. allocation does not know the dataset modality, so reserve the larger
378
+ # of one text ce chunk and one dense image loss forward/backward peak.
382
379
  completion = opd_completion_len(max_tokens, thinking)
383
- # run_opd backprops a small loss microbatch. Budget that actual dense-logit microbatch, not
384
- # the full rollout/teacher batch and not a single sequence.
385
380
  loss_mb = opd_loss_microbatch(params_b, batch_size, group_size)
386
381
  activations = loss_mb * _ACT_COEF * (seq_len / 1024.0) * width
387
- # Dense-logit peak spans BOTH the forward and the loss BACKWARD (OPD has no fused CE):
388
- # - forward: bf16 full-sequence logits [seq, vocab] (seq * 2)
389
- # fp32 completion rows [completion, vocab] for logsumexp, saved for backward
390
- # (completion * 4)
391
- # - backward: fp32 gradient of those completion rows [completion, vocab]
392
- # (completion * 4)
393
- # bf16 gradient scattered back into the full logits [seq, vocab] to reach lm_head
394
- # (seq * 2)
395
- # All four coexist at the backward peak. The old formula counted only the two FORWARD buffers,
396
- # so a long-completion / large-vocab (248k) opd job under-budgeted the loss backward by
397
- # ~(completion*4 + seq*2)*vocab bytes and could route to a GPU that OOMs in OPD loss backward
398
- # (codex[bot]). Mirror the SFT dense-logit sizing by budgeting the backward buffers too.
399
- logits_fwd = loss_mb * (seq_len * 2 + completion * 4) * vocab
400
- logits_bwd = loss_mb * (seq_len * 2 + completion * 4) * vocab
401
- logits = (logits_fwd + logits_bwd) / 1e9
382
+ ce_rows = min(OPD_CE_CHUNK_SIZE, loss_mb * completion)
383
+ chunked_logits = ce_rows * vocab * _OPD_CE_PEAK_BYTES_PER_LOGIT / 1e9
384
+ dense_image_logits = (seq_len * 4 + completion * 8) * vocab / 1e9
385
+ logits = max(chunked_logits, dense_image_logits)
402
386
  return base + rollout + activations + logits
403
387
  # Actual TRL SFT keeps fused CE disabled (see worker/sft.py), so dense logits materialize even
404
388
  # for long-context / >=3B models. Callers can pass sft_fused_ce=True only for theoretical
@@ -46,7 +46,7 @@ from flash.engine.structured_outputs import (
46
46
  parse_structured_outputs,
47
47
  reasoning_parser_for,
48
48
  )
49
- from flash.engine.vram import opd_completion_len
49
+ from flash.engine.vram import OPD_CE_CHUNK_SIZE, opd_completion_len
50
50
  from flash.engine.worker._pkg import W as _w
51
51
  from flash.engine.worker.heartbeat import liveness_heartbeat
52
52
  from flash.engine.worker.hf import RequiredSaveError, _deployable_adapter_on_hf
@@ -1645,7 +1645,9 @@ def run_opd():
1645
1645
  "opd_teacher_workers": max_teacher_workers,
1646
1646
  "opd_teacher_batch_size": teacher_batch_size,
1647
1647
  "opd_loss_microbatch_size": loss_microbatch_size,
1648
- "opd_full_logits_batches": getattr(model, "_flash_opd_full_logits_batches", 0),
1648
+ "opd_completion_only_batches": getattr(
1649
+ model, "_flash_opd_completion_only_batches", 0
1650
+ ),
1649
1651
  # Multi-turn: each assistant turn is distilled independently against its transcript-so-far
1650
1652
  # prefix, so a "sample" is a TURN, not a whole episode. Report the mode + turn ceiling and
1651
1653
  # the mean turns/episode so a run that collapsed to one-turn episodes (env never replied /
@@ -2193,6 +2195,88 @@ def _forward_logits(
2193
2195
  raise
2194
2196
 
2195
2197
 
2198
+ class _LmHeadBypass(Exception):
2199
+ pass
2200
+
2201
+
2202
+ def _forward_completion_hidden_states(
2203
+ model, input_ids, attention_mask, position_ids, *, logits_to_keep: int
2204
+ ):
2205
+ """Run the causal body through the normal model boundary but stop before the LM-head matmul."""
2206
+ output_embeddings = model.get_output_embeddings()
2207
+ input_embeddings = model.get_input_embeddings()
2208
+ if output_embeddings is None or output_embeddings is input_embeddings:
2209
+ raise RuntimeError("opd chunked ce requires a distinct output embedding module")
2210
+
2211
+ captured = {}
2212
+ marker = object()
2213
+
2214
+ def _capture_hidden(_module, args):
2215
+ if not args:
2216
+ raise RuntimeError("opd lm head received no hidden states")
2217
+ captured["hidden_states"] = args[0]
2218
+ raise _LmHeadBypass(marker)
2219
+
2220
+ handle = output_embeddings.register_forward_pre_hook(_capture_hidden)
2221
+ try:
2222
+ _forward_logits(
2223
+ model,
2224
+ input_ids,
2225
+ attention_mask,
2226
+ position_ids=position_ids,
2227
+ logits_to_keep=logits_to_keep,
2228
+ )
2229
+ except _LmHeadBypass as exc:
2230
+ if not exc.args or exc.args[0] is not marker:
2231
+ raise
2232
+ else:
2233
+ raise RuntimeError("opd could not intercept the lm head for chunked ce")
2234
+ finally:
2235
+ handle.remove()
2236
+
2237
+ hidden_states = captured.get("hidden_states")
2238
+ if hidden_states is None or hidden_states.shape[1] != logits_to_keep:
2239
+ raise RuntimeError("opd model did not honor completion-only logits_to_keep")
2240
+ return hidden_states, output_embeddings
2241
+
2242
+
2243
+ def _chunked_token_logps(lm_head, hidden_states, token_ids, *, chunk_size=OPD_CE_CHUNK_SIZE):
2244
+ """Compute exact realized-token logprobs with checkpointed vocabulary chunks."""
2245
+ import torch
2246
+ import torch.nn.functional as F
2247
+ from torch.utils.checkpoint import checkpoint
2248
+
2249
+ if hidden_states.ndim != 2 or token_ids.ndim != 1:
2250
+ raise ValueError("opd chunked ce expects [tokens, hidden] states and [tokens] ids")
2251
+ if hidden_states.shape[0] != token_ids.shape[0]:
2252
+ raise ValueError("opd chunked ce hidden-state and token counts differ")
2253
+ if hidden_states.shape[0] == 0:
2254
+ return hidden_states.new_empty((0,), dtype=torch.float32)
2255
+
2256
+ size = max(1, int(chunk_size))
2257
+
2258
+ def _logps(hidden_chunk, ids_chunk):
2259
+ logits = lm_head(hidden_chunk).float()
2260
+ return -F.cross_entropy(logits, ids_chunk, reduction="none")
2261
+
2262
+ chunks = []
2263
+ for start in range(0, hidden_states.shape[0], size):
2264
+ hidden_chunk = hidden_states[start : start + size]
2265
+ ids_chunk = token_ids[start : start + size]
2266
+ if torch.is_grad_enabled():
2267
+ chunks.append(
2268
+ checkpoint(
2269
+ _logps,
2270
+ hidden_chunk,
2271
+ ids_chunk,
2272
+ use_reentrant=False,
2273
+ )
2274
+ )
2275
+ else:
2276
+ chunks.append(_logps(hidden_chunk, ids_chunk))
2277
+ return torch.cat(chunks)
2278
+
2279
+
2196
2280
  def _bump_model_counter(model, name: str, inc: int = 1) -> None:
2197
2281
  with contextlib.suppress(Exception):
2198
2282
  setattr(model, name, int(getattr(model, name, 0) or 0) + int(inc))
@@ -2286,23 +2370,46 @@ def _resolve_samples_batched(
2286
2370
  mb = max(1, int(microbatch))
2287
2371
  for start in range(0, len(text_prepared), mb):
2288
2372
  chunk = text_prepared[start : start + mb]
2289
- _bump_model_counter(model, "_flash_opd_full_logits_batches")
2373
+ _bump_model_counter(model, "_flash_opd_completion_only_batches")
2290
2374
  seqs = [list(p.prompt_ids) + list(p.student_ids) for p in chunk]
2291
2375
  max_len = max(len(seq) for seq in seqs)
2376
+ max_completion = max(len(p.student_ids) for p in chunk)
2377
+ logits_to_keep = max_completion + 1
2292
2378
  input_ids = torch.full((len(seqs), max_len), pad_id, dtype=torch.long, device=device)
2293
2379
  attention_mask = torch.zeros((len(seqs), max_len), dtype=torch.long, device=device)
2294
2380
  for row, seq in enumerate(seqs):
2295
- input_ids[row, : len(seq)] = torch.tensor(seq, dtype=torch.long, device=device)
2296
- attention_mask[row, : len(seq)] = 1
2297
- logits = _forward_logits(model, input_ids, attention_mask)
2298
- losses = []
2381
+ offset = max_len - len(seq)
2382
+ input_ids[row, offset:] = torch.tensor(seq, dtype=torch.long, device=device)
2383
+ attention_mask[row, offset:] = 1
2384
+ position_ids = attention_mask.cumsum(dim=-1) - 1
2385
+ position_ids.masked_fill_(attention_mask == 0, 0)
2386
+ hidden_tail, lm_head = _forward_completion_hidden_states(
2387
+ model,
2388
+ input_ids,
2389
+ attention_mask,
2390
+ position_ids,
2391
+ logits_to_keep=logits_to_keep,
2392
+ )
2393
+ selected_hidden = []
2394
+ selected_ids = []
2299
2395
  for row, p in enumerate(chunk):
2300
- prompt_len = len(p.prompt_ids)
2301
2396
  comp_len = len(p.student_ids)
2302
- rows = logits[row, prompt_len - 1 : prompt_len - 1 + comp_len]
2303
- loss = _gkd_loss_from_logits_rows(
2304
- rows, p.student_ids, p.groups, kl_coef=knobs.kl_coef
2397
+ selected_hidden.append(
2398
+ hidden_tail[row, logits_to_keep - comp_len - 1 : logits_to_keep - 1]
2305
2399
  )
2400
+ selected_ids.extend(int(token_id) for token_id in p.student_ids)
2401
+ flat_logps = _chunked_token_logps(
2402
+ lm_head,
2403
+ torch.cat(selected_hidden, dim=0),
2404
+ torch.tensor(selected_ids, dtype=torch.long, device=device),
2405
+ )
2406
+ losses = []
2407
+ token_offset = 0
2408
+ for p in chunk:
2409
+ comp_len = len(p.student_ids)
2410
+ sp_t = flat_logps[token_offset : token_offset + comp_len]
2411
+ token_offset += comp_len
2412
+ loss = _gkd_loss_from_logps(sp_t, p.groups, kl_coef=knobs.kl_coef)
2306
2413
  if loss is None:
2307
2414
  results[p.idx] = SampleResult(
2308
2415
  teacher_status="ok",
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "freesolo-flash-dev"
3
- version = "1.0.6"
3
+ version = "1.0.7"
4
4
  description = "Flash — managed LoRA post-training (SFT/GRPO/OPD) for Freesolo environments, driven by the `flash` CLI"
5
5
  readme = "README.md"
6
6
  # RunPod Flash supports Python 3.11-3.12 (not 3.13 yet).
@@ -116,7 +116,7 @@ artifacts = ["flash/py.typed"]
116
116
  # auto-published by .github/workflows/publish-dev.yml: a push to `dev` whose version isn't yet on
117
117
  # PyPI publishes it; pushes that don't change it find it already published and no-op. So cutting a
118
118
  # dev-channel release = bump this (and [project].version) and merge to `dev`.
119
- version = "1.0.6"
119
+ version = "1.0.7"
120
120
 
121
121
  [tool.pytest.ini_options]
122
122
  pythonpath = ["."]
@@ -468,6 +468,38 @@ def test_estimator_logits_term_uses_max_tokens_and_caps_at_budget():
468
468
  assert e(2.0, "sft", seq_len=4096, max_tokens=256) == e(2.0, "sft", seq_len=4096, max_tokens=8192)
469
469
 
470
470
 
471
+ def test_opd_vram_estimate_reserves_one_dense_image_loss_peak():
472
+ from flash.engine.vram import estimate_vram_gb as e
473
+
474
+ seq_len = 4096
475
+ completion = 512
476
+ vocab = 248_320
477
+ with_vocab = e(
478
+ 2.0,
479
+ "opd",
480
+ seq_len=seq_len,
481
+ max_tokens=completion,
482
+ vocab=vocab,
483
+ batch_size=4,
484
+ group_size=4,
485
+ )
486
+ without_vocab = e(
487
+ 2.0,
488
+ "opd",
489
+ seq_len=seq_len,
490
+ max_tokens=completion,
491
+ vocab=0,
492
+ batch_size=4,
493
+ group_size=4,
494
+ )
495
+ expected = (seq_len * 4 + completion * 8) * vocab / 1e9
496
+ assert with_vocab - without_vocab == pytest.approx(expected)
497
+
498
+ short = e(2.0, "opd", seq_len=seq_len, max_tokens=128, vocab=vocab)
499
+ long = e(2.0, "opd", seq_len=seq_len, max_tokens=2048, vocab=vocab)
500
+ assert long > short
501
+
502
+
471
503
  def test_open_model_opd_uses_opd_sizing_not_grpo(monkeypatch):
472
504
  """Regression (codex[bot], vram.py): for an uncataloged (model_policy='allow') model the open-model
473
505
  fallback hardcoded ``_need(params_b, 'grpo', ...)``, so an OPD run was sized as a colocated-vLLM GRPO
@@ -629,11 +661,11 @@ def test_observed_qwen2_opd_vllm_case_routes_off_32gb_cards(monkeypatch):
629
661
  "lora_rank": 32,
630
662
  "lora_alpha": 64,
631
663
  },
632
- "RTX Pro 6000",
664
+ "A100 PCIe",
633
665
  ),
634
666
  (
635
- # fp8 KV (cc >= 8.9) halves the resident rollout KV, so this fits the 141 GB H200 and no
636
- # longer needs the 180 GB B200 — still far off the 32/40 GB consumer classes this guards.
667
+ # the dense image fallback grows with context and keeps this mixed-modality-safe route
668
+ # above the 96 gb class.
637
669
  "max_context_16384",
638
670
  {
639
671
  "epochs": 1,
@@ -653,7 +685,7 @@ def test_observed_qwen2_opd_vllm_case_routes_off_32gb_cards(monkeypatch):
653
685
  "lora_rank": 32,
654
686
  "lora_alpha": 64,
655
687
  },
656
- None,
688
+ "H200",
657
689
  ),
658
690
  (
659
691
  "group_size_8",
@@ -748,9 +780,8 @@ def test_observed_qwen4b_opd_vllm_startup_case_routes_off_40gb_cards(monkeypatch
748
780
  }
749
781
 
750
782
  need = required_vram_gb("Qwen/Qwen3.5-4B", "opd", train=train)
751
- # Off the 40 GB class, but no longer forced onto the 141 GB H200: the colocated OPD rollout uses an
752
- # fp8 KV cache on cc >= 8.9 hardware, and a run this size can only land on a modern (cc >= 8.9)
753
- # card, so its KV pool is fp8-sized — it fits the cheaper 96 GB RTX Pro 6000 (Blackwell).
783
+ # the dense image fallback keeps this run above the 80 gb class while still fitting the 96 gb
784
+ # rtx pro 6000.
754
785
  assert 80 < need <= 96
755
786
 
756
787
  preview_gpu = provisional_gpu("Qwen/Qwen3.5-4B", "opd", train=train)
@@ -480,3 +480,33 @@ def test_estimate_exact_pinned_provider_routes_through_disk_aware_allocate(monke
480
480
  assert seen["disk_gb"] == 200.0
481
481
  # and the quote reflects the live allocation rather than a static gpu_hourly_usd lookup.
482
482
  assert (est.provider, est.gpu, est.gpu_hourly_usd) == ("runpod", "H100", 3.29)
483
+
484
+
485
+ def test_b200_not_cheaper_or_faster_than_h200_for_grpo(monkeypatch):
486
+ # regression: the estimator must not advertise b200 as faster/cheaper than h200 on peak flops.
487
+ # b200/sm100 training is h200-class (portable kernels), so at its higher $/hr b200 must never
488
+ # come out cheaper, and never faster, than h200 for the same run.
489
+ from types import SimpleNamespace
490
+
491
+ import flash.providers.allocator as allocator_mod
492
+ from flash.cost.facts import gpu_hourly_usd
493
+
494
+ def alloc_as(gpu):
495
+ rate = gpu_hourly_usd(gpu)
496
+
497
+ def fake_allocate(model_id, method, **kwargs):
498
+ return SimpleNamespace(gpu=gpu, provider="runpod", hourly_usd=rate, min_vram_gb=80)
499
+
500
+ return fake_allocate
501
+
502
+ monkeypatch.setattr(allocator_mod, "allocate", alloc_as("H200"))
503
+ h200 = estimate_cost(RunConfig("Qwen/Qwen3.5-4B", "grpo", 100, exact_type="H200"))
504
+ monkeypatch.setattr(allocator_mod, "allocate", alloc_as("B200"))
505
+ b200 = estimate_cost(RunConfig("Qwen/Qwen3.5-4B", "grpo", 100, exact_type="B200"))
506
+
507
+ assert gpu_hourly_usd("B200") > gpu_hourly_usd("H200") # b200 is the pricier card
508
+ # same effective training throughput => b200 is no faster than h200 ...
509
+ assert b200.seconds_per_step == pytest.approx(h200.seconds_per_step)
510
+ assert b200.train_seconds == pytest.approx(h200.train_seconds)
511
+ # ... and at its higher $/hr, never cheaper.
512
+ assert b200.total_usd > h200.total_usd
@@ -100,3 +100,22 @@ def test_pick_gpu_impossible_raises():
100
100
  def test_pick_gpu_auto_matches_default():
101
101
  assert pick_gpu(24, provider="auto") == pick_gpu(24)
102
102
  assert pick_gpu(12) == "RTX 4090"
103
+
104
+
105
+ def test_effective_train_tflops_caps_b200_at_h200_class():
106
+ # b200/sm100 training falls back to portable kernels, so realized training throughput is
107
+ # h200-class, not the 2.25 pflops peak. the cost model must not treat b200 as faster than h200.
108
+ from flash.cost.facts import effective_train_tflops
109
+
110
+ assert gpu_tflops("B200") == 2250.0 # raw peak unchanged (vram/serving still use gpu_tflops)
111
+ assert effective_train_tflops("B200") == 990.0
112
+ assert effective_train_tflops("B200") == effective_train_tflops("H200")
113
+
114
+
115
+ def test_effective_train_tflops_is_peak_for_uncapped_classes():
116
+ from flash.cost.facts import effective_train_tflops
117
+
118
+ for name in ("H100", "H200", "A100 SXM", "RTX 4090", "B200"):
119
+ # only b200 is capped; every other class keeps its peak.
120
+ expected = 990.0 if name == "B200" else gpu_tflops(name)
121
+ assert effective_train_tflops(name) == expected